【问题标题】:How secure are values returned from methods?从方法返回的值有多安全?
【发布时间】:2012-11-16 20:48:59
【问题描述】:

我正在为一个应用程序做一些基本的安全工作。当用户登录时,他们的凭据通过 Active Directory 进行验证。有时,用户请求更改导致程序重新启动。由于此应用程序不是单实例程序,因此我只需启动另一个实例并关闭当前实例。应用程序中的一切都很好。

但是,用户对每次重新启动都必须重新登录感到不满。所以我拼凑了一些基本的安全性,使用 SecureString 将密码存储在应用程序中。如果应用程序重新启动,密码会被解密,然后使用 CodeProject 中 Rijndael 算法的基本实现重新加密。应用程序将用户名和加密密码作为命令行参数传递给新实例。应用程序需要加密密码,因为任何进一步的wmic process 调用都会显示密码。新实例解密密码,以静默方式针对 Active Directory 进行验证,并像往常一样将其存储为 SecureString。

我对一般安全实践不太熟悉。从解密方法返回密码我有点紧张。它本身没有存储在任何变量中,因为调用是在 Active Directory 验证请求中进行的。我仍然不确定返回的密码是否可以在内存中的某个地方访问,或者它是否存储在寄存器中。

这个过程不需要是有史以来最强大的安全性。如果人们可以轻松访问内存内容,它只需要阻止人们获取明文密码。

非常感谢!

【问题讨论】:

  • 问题不在于传递凭据 - 在于解密它们,原始实例无论如何都必须这样做。解密后的字符串不会挂在寄存器中,因为String 是引用类型而不是值类型 - 解密后的字符串将在堆中并且可供内存窥探者访问。
  • 我明白了。好吧,既然它无论如何都是可见的,我是否应该忘记 SecureString 并将密码以明文形式存储在应用程序中,并且只在需要将其作为命令参数传递时使用基本的 Rijndael? (只是因为有人可以很容易地坐在任何人的电脑前并使用 wmic 进程查看它)。
  • 计算机安全中最重要的是知道最薄弱的地方在哪里。如果任何人都可以坐在已登录的计算机前,他们就可以插入 U 盘并进行各种欢乐的破坏。 SecureString 旨在允许加密的密码从 Web 应用程序的 web.config 文件传输到 SQL Server 而无需被解密 - 在 Windows 窗体应用程序中,您别无选择,只能最终解密它,因此请尽量保留它记住尽可能少的时间。
  • 哦,好吧,有道理。如果我的理解正确,无论我采用何种加密方法,解密时(因为它是一个字符串)它总是可以在内存中访问,直到它被覆盖(不应该有任何东西导致它持续存在)。我想在那种情况下,由于它仍然必须以某种方式存储在应用程序中,我仍然会丢弃 SecureString,但我也会对其进行 Rijndael 加密,然后将其存储在程序中。在某种程度上,它变得更容易了,因为现在我不必从 SecureString 到 Rijndael,然后再返回。谢谢你,你是最有帮助的。
  • 活动目录不支持使用ticket吗?这样您就可以凭票重新登录?

标签: c# security .net-3.5


【解决方案1】:

你的攻击模型是什么?

  1. 如果您假设服务器归攻击者所有,您在 所有情况。
  2. 如果您认为服务器是安全的,则不需要任何 完全加密。

所以我想我的建议是放弃你正在做的所有服务器端加密,因为你假设服务器无论如何都是安全的。没有人可以访问您服务器的内存,如果有人可以,您无论如何都会被拥有。

【讨论】:

  • 我根本不同意这一点。你应该尽可能地增加防御,这样,如果一个防御被克服,就会有另一个等待(也称为纵深防御)。不过,公平地说,我没有更好的选择。
  • @zimdanen 总的来说,您的陈述是正确的,但是针对所拥有的服务器建立防御是没有希望的。这是一个完全不现实的情况来防御。将您的时间投入到其他可以获得回报的地方,例如投入相同的时间来保护服务器。
  • 平心而论,这主要是为了阻止任何会坐在另一位员工的计算机前并尝试轻松获取密码的脚本小子。所以从这个意义上说,保护应用程序本身不会轻易放弃它实际上是这里的目标。所以我想对用户密码进行某种保护,尤其是当它作为命令参数传递时。
【解决方案2】:

在 .NET 应用程序中,函数返回值被推送到“评估堆栈”,该堆栈驻留在进程内的受保护内存中。但是,您正在谈论的是一个字符串,这是一个引用类型,因此评估堆栈上的内容是指向该字符串在堆上的位置的指针。堆内存相对不安全,因为它可以共享,而且只要 GC 认为不需要收集它,它就会存在,这与高度易失的评估或调用堆栈不同。但是,要访问堆内存,该内存必须是共享的,并且您的攻击者必须拥有一个获得操作系统和 CLR 许可的应用程序才能访问该内存,并且该应用程序知道在哪里查找。

如果攻击者拥有这种访问权限,则有更多更简单的方法可以从计算机获取明文密码。键盘记录器可以查看输入的密码,或者另一个窥探者可以查看 GDI UI 的非托管端的实际句柄,并查看实际显示在 Windows GUI 中的文本框获取明文值(它只是在显示器上被混淆)。所有这一切都无需尝试破解 .NET 的代码访问安全性或受保护的内存。

如果你的攻击者有这种控制,你就输了。因此,这应该是第一道防线;确保客户端计算机上没有此类恶意软件,并且用户尝试登录的客户端应用程序实例没有被破解的相似对象替换。

就实例之间的模糊密码存储而言,如果您担心内存窥探,那么像 Rijndael 这样的对称算法是无法防御的。如果您的攻击者可以看到客户端计算机的内存,他就知道用于加密它的密钥,因为您的应用程序需要知道它才能解密它;因此,它将被硬编码到客户端应用程序中,或者将存储在安全字符串附近。同样,如果您的攻击者拥有这种控制权,那么您在客户端进行身份验证时就失败了。

相反,我会使用物理和电子安全机器上的服务层来提供应用的任何功能,如果被攻击者滥用(主要是数据检索/修改)会对您造成伤害。该服务层可用于验证和授权用户执行客户端应用程序允许的任何操作。

考虑以下几点:

  • 用户将他们的凭据输入到您的客户端应用程序中。这些凭据可以与 AD 凭据相同,但不会照此使用。防止键盘记录器或其他恶意软件看到这一点的唯一方法是通过强制执行良好的 AV 软件来确保计算机上不存在此类恶意软件。
  • 客户端应用程序通过 WCF 连接到您的服务终结点。端点可以使用 X.509 证书进行签名;不是 NSA 级别的安全性,但至少您可以确信您正在与您控制的服务器通信。
  • 然后,客户端应用程序会使用可生成大摘要的内容(例如 SHA-512)对您的用户密码进行哈希处理。这本身并不安全;它太快并且用户密码的熵太低,无法防止攻击者破解哈希。但是,同样,他们必须控制计算机才能查看哈希,我们将进一步对其进行混淆。
  • 客户端应用通过 WCF 通道传输客户端计算机的用户名、密码和硬件 ID。
  • 服务器获取这些凭据。请注意,服务器没有得到明文密码;这是有原因的。
  • 服务器将散列密码分成 256 位的两半。然后对前半部分进行 BCrypted(使用配置为适当慢的实现;通常需要 10 或 11 个“轮”),并与用户数据库中的散列值进行比较。如果它们匹配,则数据库返回用户的 AD 凭据,这些凭据已使用另一半密码哈希进行对称加密。这就是为什么永远不会发送明文密码的原因;服务器不必知道它,但攻击者会为了从被盗的用户数据库副本中获取任何有意义的东西。
  • 服务器解密 AD 凭据,将其提交给 AD,并接收代表该用户身份和安全上下文的 IPrincipal。 IPrincipal 实现将包含可用于破解用户帐户的零信息。
  • 服务器生成一个加密随机的 128 位值,连接 128 位硬件 GUID,并使用 SHA512 对其进行哈希处理。它使用该哈希的一半来对称加密用于解密 AD 凭据的密钥值。然后它对另一半进行 BCrypt,并将该哈希存储在加密密钥旁边。
  • 然后服务器通过安全 WCF 通道传回三条信息; AD 生成的 IPrincipal、未散列的 128 位随机值(“传输令牌”)和另一个任意长度的加密随机值(“会话令牌”)。
  • 客户端应用程序现在在客户端进行了身份验证,这意味着您可以通过询问 IPrincipal 以获得 AD 角色成员身份来控制用户对代码的访问,并且服务器现在也确信拥有会话令牌的用户是真实用户.在对服务进行任何进一步调用(数据检索/持久性)时,客户端应使用已协商的 WCF 通道,并传递其会话令牌。 WCF 通道和会话令牌的组合是一次性且唯一的;在新通道上使用旧令牌,或在同一通道上传递错误令牌,表明会话已被破坏。最重要的是,无论何时何地,存储在客户端或服务器中的任何持久性数据都不能用于获取 AD 凭据并进行身份验证。

现在,当您的客户端应用程序关闭时,客户端和服务器之间的所有“会话状态”都会丢失;会话令牌对任何其他协商通道均无效。所以,你失去了身份验证;下一个连接的客户可能是任何人,无论他们说自己是谁。这就是“转移令牌”的用武之地:

  • “传输令牌”是返回系统的免费通行证。它是一次性的,如果在发行 18 小时后未使用,则会过期。
  • 客户端应用程序在关闭时将两条信息传递给新实例(不管它选择这样做);登录人的用户名,以及“转账令牌”。
  • 客户端应用程序的新实例获取这两条信息,并且还获取客户端计算机的硬件ID。它与 WCF 服务协商安全连接,并传递这三个信息。
  • 如果用户上次登录时间超过 18 小时(不是 24 小时,所以他们不能比昨天早一分钟出现并重新启动应用程序),或者如果你真的很偏执,超过 8几小时前,该应用立即返回该帐户的转移令牌已过期的错误。
  • 服务获取传输令牌,连接硬件 ID,SHA-512s,BCrypts 一半,并将结果与​​存储的第二个验证值进行比较。只有传输令牌和最后登录的机器的正确组合才会产生正确的哈希值。如果匹配,则使用哈希的另一半解密密钥,然后解密 AD 信息。
  • 然后服务继续进行,就像用户提供了应用程序密码哈希、解密 AD 信息、检索 IPrincipal、生成新的传输令牌、会话令牌并重新加密 AD 数据的密钥一样。
  • 如果此过程的任何部分失败(尝试使用不正确的令牌,包括两次使用相同的令牌,或者使用来自不同机器或不同用户的令牌),服务会报告凭据无效。然后,客户端应用将回退到标准的用户密码验证。

这就是问题所在;这个系统依靠一个除了用户的头脑之外不存在任何地方的秘密密码,没有后门;管理员无法找回丢失的客户端应用密码。此外,必须更改 AD 凭据时,只能从客户端应用程序中更改;用户不能在 Windows 登录时被 AD 本身强制更改其密码,因为这样做会破坏他们进入客户端应用程序所需的身份验证方案(加密的凭据将不再有效,并且客户端应用程序凭据是需要重新加密新的)。如果您能够以某种方式在 AD 中拦截此验证,并且客户端的应用程序凭据是 AD 凭据,您可以自动更改用户应用程序中的凭据,但现在您使用一组凭据来混淆同一组凭据,如果知道这个秘密,你就完蛋了。

最后,这个安全系统的变体仅在一个原则上起作用;服务器当前没有被攻击者入侵。有人可以进去,下载离线数据,然后他们就卡住了;但是如果他们可以安装一些东西来监控内存或流量,你就会被淹没,因为当凭据(用户名/密码哈希或传输令牌/硬件 ID)进入并经过验证时,攻击者现在拥有解密用户的密钥AD 凭据。通常情况下,客户端从不发送解密密钥,只发送哈希密码的验证一半,然后服务器发回加密的凭据;但是,您认为客户端比服务器具有更大的安全风险,所以只要这是真的,最好在客户端上尽可能少地保留任何时间长度的纯文本。

【讨论】:

  • 感谢您回答我最初的问题并提供如此详细的进一步行动方案。奇怪的是,有些系统被锁定在机箱内,只有我们/其他 IT 人员可以访问。不幸的是,你所提议的听起来像是对程序的一个相当密集的改变。是的,正在使用的密钥只是当前用户的 ID + 日期,并且仅与其他任何内容一样硬编码(在加密/解密时间计算而不存储),使其在第二天无效。我可能会把已经完成的工作留在里面,但我现在明白,要想走得更远,就需要全力以赴。
  • 差不多。对于大多数公司来说,要求从硬连线 LAN 或通过 VPN 登录系统就足够了,以便访问客户端应用程序的数据存储区(没有它,客户端几乎毫无用处)。
【解决方案3】:

看看这个: Password Salt (Wikipedia)

总结一下方法:

  1. 当您想将密码 P 存储到磁盘时,生成一个随机字符串 S(称为 salt),然后存储元组 (S, SHA1Hash(P + S))。
  2. 如果您想根据存储的密码检查密码尝试 P',请将 SHA1Hash(P' + S) 与存储的哈希值进行比较。
  3. 当您传递此密码尝试时,仅传递散列版本,即 SHA1Hash(P' + S)。

【讨论】:

  • 这很好,但我认为它不符合我的要求。散列更多地用于保留密码以供以后比较。我正在尝试保留密码,以便在应用程序本身调用重新启动时实际重新使用相同的密码进行自动登录。所以散列它对我没有多大好处,因为我需要能够取回实际密码以使用 AD 进行验证。不过还是谢谢! :)
  • 如果你这样做并且想使用 SHA1,你至少应该实现 PKDF2,而不是单个 SHA1 哈希。
猜你喜欢
  • 2013-02-12
  • 1970-01-01
  • 2012-01-19
  • 2012-07-04
  • 2015-03-14
  • 1970-01-01
  • 2010-12-14
  • 1970-01-01
相关资源
最近更新 更多