安全忘记密码页面:服务器无状态实现是否可行?

信息安全 Web应用程序 应用安全
2021-09-02 12:21:40

这是另一个“忘记密码页面如何正确完成”的问题。但是,它明确地解决了我最近遇到的两个实现选项。

我知道的常用方法 [1,2] 是基于只能使用一次且有效性有限的随机令牌:

  • 用户访问忘记密码页面
  • 用户提供电子邮件地址(或类似地址)
  • 如果电子邮件与有效的用户帐户相关联
    • 该应用程序生成一个随机的、唯一的令牌 t。t 具有足够的熵(比如 128 位)。
    • 该应用程序将随机令牌 t 和创建它的时间戳与用户一起存储在数据库中
    • 该应用程序会向用户的电子邮件地址发送一封包含密码重置链接的电子邮件。链接看起来像这样:https ://foo.bar/resetPassword?token=t
  • 用户点击电子邮件中的链接
  • 该应用程序检查
    • 如果给定用户的数据库中有令牌 t
    • 如果是,则根据存储的时间戳检查它是否仍然有效
    • 如果是,则允许用户访问 resetPassword 页面
  • 用户设置新密码
  • 之后,应用程序使令牌 t 无效

您可以添加更多安全内容,例如带外频道或安全问题。但对我来说,这是我所知道的常见最佳实践。

现在考虑一种不同的方法。我们将失去一次性使用属性。但是,好处是应用程序根本不需要跟踪令牌状态。

我们将使用 HMAC,而不是生成随机令牌,如下所示:

  • 令牌 = HMAC_k(电子邮件,时间戳)

k 是应用程序知道足够熵的秘密。通过电子邮件发送给用户的重置链接如下所示:

当用户访问该链接时,应用程序会按如下方式对其进行验证:

  • 根据 URL 参数计算 HMAC:token_calculated = HMAC_k(email, timestamp)
  • 检查作为 URL 参数提供的令牌和计算的 HMAC 是否匹配
  • 如果是,则根据时间戳检查令牌是否仍然有效
  • 如果是,则允许用户访问 resetPassword 页面

在我看来,不必维护服务器端状态的属性比单次使用属性更有价值。这假设在服务器端配置了一个合理的、短暂的令牌有效期——比如 20 到 30 分钟。

我真的很想知道其他人对第二种方法的看法。特别是,如果有任何我可能还没有注意到的缺点。

[1] https://www.owasp.org/index.php/Forgot_Password_Cheat_Sheet

[2] http://www.troyhunt.com/2012/05/everything-you-ever-wanted-to-know.html

2个回答

你的问题写得很好,你清楚地理解了这些问题。您描述的加密令牌方法没有根本性的错误。需要注意的一些事项:

  • 如今,大多数网站都是数据库密集型的,因此存储密码重置令牌几乎没有额外的开销。
  • 您应该限制可以生成的密码重置电子邮件的数量(例如,对于每个电子邮件地址,每 30 分钟最多 2 封) - 这需要写入数据库。
  • 你依赖于 k 剩下的秘密;这可能被恶意管理员复制(或暴力强制,如果弱) - 而数据库令牌避免了这种单点故障。
  • 密码重置方案的一个常见(尽管很小)弱点是令牌泄漏到引用标头中的外部站点。让令牌严格单独使用可以帮助解决这个问题 - 尽管您也可以使用加密令牌来解决这个弱点。

生成会话 cookie 也有类似的选择——使用加密或数据库。事实上,对于会话 cookie,性能考虑更为重要,因为这项工作是针对每个请求完成的。尽管开销很大,但大多数站点使用数据库令牌,以获得较小的安全优势。

没有必要失去一次性财产。想象一下密码重置令牌是(从概念上讲,添加详细信息):HMAC(email, timestamp, current_password_hash),其中当前密码哈希是您存储在数据库中的哈希(希望基于某些 KDF),这是用户不知道。

当用户更改密码时,此哈希值将不同,因此令牌将不再有效。即使用户设置了相同的密码(虽然不太可能,因为他们应该尝试过),密码散列的正确实现也应该包括一个随机数,并且密码散列在重置后会有所不同,即使密码相同。这基本上是利用了您已经在数据库中存储了一些状态(密码哈希)并且您可以跳过存储另一个状态的事实。

PS k 真的应该保密 - 这是一个主要问题:)