4

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

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

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

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

非常感谢!

4

3 回答 3

4

你的攻击模型是什么?

  1. 如果您假设服务器归攻击者所有,那么您在所有情况下都会丢失。
  2. 如果您假设服务器是安全的,则根本不需要任何加密。

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

于 2012-11-16T20:52:57.607 回答
1

在 .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 凭据的密钥值。然后它对另一半进行加密,并将该哈希存储在加密密钥旁边。
  • 然后,服务器通过安全 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 凭据。通常情况下,客户端从不发送解密密钥,只发送哈希密码的验证一半,然后服务器发回加密的凭据;但是,您认为客户端比服务器具有更大的安全风险,所以只要这是真的,它'

于 2012-11-16T23:10:30.293 回答
0

看看这个: 密码盐(维基百科)

总结一下方法:

  1. 当您想将密码 P 存储到磁盘时,生成一个随机字符串 S(称为 salt),然后存储元组 (S, SHA1Hash(P + S))。
  2. 当您想根据存储的密码检查密码尝试 P' 时,请将 SHA1Hash(P' + S) 与存储的哈希值进行比较。
  3. 当您传递这个密码尝试时,只传递散列版本,即 SHA1Hash(P' + S)。
于 2012-11-16T22:23:13.033 回答