【问题标题】:Time limited, or one time use, password reset tokens?限时或一次性使用的密码重置令牌?
【发布时间】:2013-10-16 05:38:13
【问题描述】:

用户忘记了密码,并且(几乎)所有会员网站都需要一种方法来帮助用户重新登录。

我想实现常见的场景:

  1. 用户访问网站,尝试登录,但无法登录,然后意识到他们忘记了密码 - 废话!
  2. 用户输入邮箱地址并点击“忘记密码”
  3. 用户收到带有密码重置链接的电子邮件

这是我打算如何实现它(C#/ASP.NET MVC):

  1. 当用户输入电子邮件并点击“忘记密码”按钮时,我的网站将生成一个 GUID,将其存储在数据库中的成员实体 (member.ResetToken) 中,并通过电子邮件向他们发送 URL 中带有该 GUID 的链接(发送的电子邮件将通知他们只能使用此链接一次)
  2. 用户单击该链接,我的网站会根据该 URL 中的 member.ResetToken 查找他们的帐户。如果找到他们的帐户,请向他们显示密码重置表单,当他们完成重置后,它会从他们的帐户中清除 member.ResetToken

这是我的问题:保持这样(他们可以在现在或将来随时使用该链接重置密码)或添加时间戳以限制他们必须重置密码的时间?

从用户体验的角度来看,在您准备就绪时重置密码的能力非常棒,但我想确保我没有忽略这可能引发的一些安全问题。

【问题讨论】:

  • 生成重置请求并通过电子邮件发送并设置时间限制,但不要更改密码。通过更改密码并发送重置,很容易爆出某人的密码:)
  • 这不是我描述的。 ;) 密码由用户更改。当他们单击“忘记密码”按钮时,会向他们发送带有密码重置令牌的 URL。 GUID 是密码重置令牌(尽管我可能不会使用 GUID,而是更随机的东西)。我在问一次性使用令牌与使用限时令牌相比是否足够安全。不过还是谢谢。
  • 是的,我明白你在说什么,看看我的回答。

标签: security passwords password-recovery


【解决方案1】:

您的方案确实有效,但有些地方可以改进。但首先是关于时间限制的原始问题:

让我们问一个相反的问题:为什么令牌应该无限期保持有效?

没有任何好处,当两年后可以点击重置链接时,用户要么在大约一个小时内点击了该链接,要么他可能已经忘记了该链接(如果需要,可以请求一个新链接)。另一方面,能够阅读电子邮件并不一定意味着攻击者必须破解电子邮件帐户,例如办公室中打开的电子邮件客户端、丢失的手机、在(丢失的)USB 驱动器上备份...

最重要的改进是,您应该只在数据库中存储令牌的散列。有权访问数据库(SQL 注入)的人可能会要求为他喜欢的任何电子邮件地址重置密码,并且因为他可以看到新令牌,所以他可以使用它来设置自己的密码。

然后我会将这些重置信息存储在单独的表中。在那里,您可以存储用户 ID、哈希令牌、到期日期以及链接是否已被使用的信息。那么用户不是处于特殊状态。

也许我误解了这一点,但是重置链接应该指向一个特殊的密码重置页面。当用户进入登录页面时,应该没有特殊处理,登录页面应该不会知道有一个未决的密码重置。

重置令牌应该是不可预测的,这可以通过从操作系统的随机源读取的真正随机代码来实现。

【讨论】:

  • 关于只存储令牌哈希的有趣想法。这将要求攻击者能够访问散列算法的密钥才能生成令牌。谢谢。
  • @nullability - 哈希算法不需要密钥,它是单向操作,因此如果您只有哈希,则无法获取原始文本。您可能想到的是加密,这是一种双向操作,需要一个密钥。
  • 您当然是对的,哈希算法本身不使用密钥,但如果您是 HMAC 函数,则可以使用密钥生成单向签名哈希。我正在考虑使用 HMAC-SHA256,例如 PHP 的 hash_hmac
  • 另外值得注意的是,如果使用这种方法,在验证hash时应该使用慢速比较函数,避免定时攻击。
  • @nullability - 是的,您可以使用 HMAC,但优势不是很大,重要的是不能伪造令牌,导致与存储的哈希相同。定时攻击在这里不是问题,因为服务器正在比较哈希值,而不是明文令牌。从相似的哈希值中,您无法得出关于标记相似性的结论。
【解决方案2】:

因此,我在评论中试图避免这种方法存在一些问题。当您将“确认令牌”存储在用户密码中时,您基本上已经销毁了他们的密码。

我,一个恶意的人,然后可以获取一个巨大的电子邮件地址列表和一个僵尸网络,并用密码重置请求淹没你的服务器,并将你的用户从他们的帐户中锁定。当然,您的用户会收到一封重置密码的电子邮件,但如果我可以足够快地重置密码,则可能会有电子邮件积压(或者,如果您同步执行此操作,我可能会拒绝整个应用程序)。

我,您系统的普通用户,可能会尝试重置我的密码,但我不知道为什么我没有收到重置电子邮件,因为我什至不知道垃圾邮件文件夹(或者它从未到达)。幸运的是,我只记得密码是什么,但它不再起作用了,因为密码现在是一个不透明的 GUID,在我找到重置电子邮件之前,我基本上已经死在水中了。

这是您应该使用的流程。

  1. 生成密码重置请求,您可以使用 GUID 查找该请求,您还可以通过使用一些私有数据对该值进行哈希处理并将其传递到 URL 中以避免快速攻击来保护此请求。您还可以通过使其仅在一定时间内有效来锁定此请求。
  2. 一旦有人使用有效令牌和您指定的任何其他参数点击该链接,他们就可以更改密码,此时您现在可以安全地更改用户密码
  3. 将密码请求标记为已完成,或将其删除。如果您真的很担心谁更改了密码,您还可以跟踪 IP 地址或类似信息。
  4. 向用户发送一封电子邮件,确认他们已更改密码。

此外,以防万一这还没有发生,请确保您对用户密码进行散列和加盐。当您只是用 GUID 替换密码时,听起来您并没有这样做,所以请仔细检查。

【讨论】:

  • 令牌不会替换用户的密码。它是成员对象 (member.ResetToken) 的单独属性。当用户单击电子邮件中的重置密码 URL 时,我的网站将通过 ResetToken 查找他们的帐户。如果找到该帐户,请向他们显示密码重置表格。会员重置密码后,请从其帐户中删除令牌。如果他们再次尝试使用重置密码 url,它将无法正常工作,因为找不到他们的帐户。谢谢,对不起,如果我的措辞让你感到困惑。
【解决方案3】:

用户也忘记了重置密码(事情发生了)。由于对密码偏执,我建议将链接生命周期限制为 24 小时。这应该绰绰有余。没有解决恶意拦截的问题,但聊胜于无。

【讨论】:

    【解决方案4】:

    我会提出以下建议:

    1. 在允许用户单击忘记密码按钮之前需要一些信息。例如,需要电子邮件地址和出生日期。

      理想情况下,您的用户界面不应提供任何反馈,让黑客可以确定他的重置请求是否成功。您不希望他们在您的页面上种植电子邮件地址或 DOB。然而,这是一个可用性权衡,所以你是否这样做取决于你真正需要多少安全性。

      您可能还考虑要求验证码,这使得暴力破解和应用程序 DoS 攻击更加困难。

    2. 尽快使一次性令牌过期。在我看来,几个小时就足够了。你永远不应该认为电子邮件是私人的——它不是,除非你在基本协议之上使用安全的电子邮件技术(例如 PGP)(大多数人不这样做)。您最不想看到的就是打开一个黑市来买卖您的 GUID,如果它们有无限的生命周期,这正是可能发生的事情。

    3. 不要使用 GUID。它们不是密码随机的并且是可猜测的。我建议您使用加密随机数生成器并将其转换为 base64。

    【讨论】:

      猜你喜欢
      • 2017-08-12
      • 2017-04-21
      • 1970-01-01
      • 1970-01-01
      • 2018-03-31
      • 2015-01-30
      • 1970-01-01
      • 1970-01-01
      • 2021-03-26
      相关资源
      最近更新 更多