减轻对密码恢复页面的定时攻击威胁

信息安全 定时攻击 用户枚举
2021-09-09 04:01:29

我们最近对我们管理的一个面向公众的网站进行了外部安全审查。他们指出,在“恢复密码页面”上,提供现有和不存在的用户名时响应时间不同。他们声称这可以使攻击者更容易测试现有用户名。消除此攻击向量的建议更改是始终为每次密码恢复尝试执行相同的逻辑。

在我们的例子中,密码恢复逻辑由(大致)以下步骤组成:

  1. 从数据库加载用户。
  2. 使用密码重置链接向用户发送电子邮件。

如果用户存在,则执行这两个步骤。如果用户不存在,则只执行第 1 步。

在数据库查找等方面,这很容易解决。但是当涉及到与外部系统的交互时,在这种情况下发送电子邮件,它就不那么简单了。

我认为引入随机延迟不会有太大帮助,因为响应时间分布可能仍然存在可检测的差异。

另一个想法是在某个地方设置一个电子邮件帐户,并为每个不存在的用户名发送一封电子邮件。

在这种情况下,最佳做法是什么?

4个回答

让我们在这里问真正的问题:

用户名列表是否与您的应用程序机密相关联?

有点,但大多没有通常,每个用户名在应用程序中都是唯一的,因此即使不是不可能,也很难保密。例如,一种经常(如果不是总是)找到与您的应用程序关联的用户名的方法是简单地创建一个新帐户。当您输入用户名时,创建过程会告诉您它是否已被使用。因此,您可以慢慢构建有效用户名列表。

总的来说,尝试将用户名保密并没有多大意义,而且可能是不可能的,所以放弃那部分吧。

有趣的阅​​读关于您的用户名如何不是秘密

应该努力保护什么?

您需要保护与用户名相关的秘密;密码和任何其他形式的身份验证。正是在这里,打扰定时攻击是有意义的。例如,如果前 2 个字符匹配,则您的密码验证码需要与完全没有字符匹配的时间相同。

附加说明

保护您的多因素身份验证是另一个有趣的话题。这不是直接的定时攻击,而是相同的原理。例如,如果您有登录应用程序所需的密码和电话代码。如果应用程序只在密码正确时询问您的电话号码,与应用程序每次都询问密码和电话号码相比,攻击者更容易找到密码。但是如果你每次都问,电话号码的隐私就有问题了……

您可以选择异步执行操作,从而呈现线性时间的外观。就伪代码而言,您总是可以这样做:

Sleep 1000 ; Sleep for a second
Output template ; Show a confirmation page
Close Socket ; Make the browser/client believe we're done
Lookup User From Database ; Try to load the user
If User Found
    Send Email ; Send an Email
End If

我知道至少有一项服务可以做到这一点;用户将始终看到一条确认消息(“太好了。现在检查您的电子邮件。”)而无需实际确认用户是否存在和/或是否已发送电子邮件。这消除了您控制下的所有可变时间,包括数据库搜索时间以及发送电子邮件所需的任何时间。

并非所有语言都直接支持这种类型的模型,但您可以使用 fork() 生成一个进程(或您的语言提供的任何方法)来异步执行代码。

这仅将时间问题留给操作系统的任务调度程序,而不是基于执行的实际逻辑。引入人为延迟也将减轻某些类型的非多线程程序,因为它们每次尝试都必须等待一秒钟(这不是一个好的防御,但许多“黑客”工具无论如何都不是大规模多线程的)。

然而,即使没有延迟,他们仍然无法确认用户帐户是否真的有效,因此攻击会得到缓解。您唯一需要做的就是稳定接收响应所需的时间;成功的查找和失败的查找应该花费完全相同的时间。

我已经通过几种方式看到了这一点,一种你已经提到过使用随机时间加法(我不喜欢这个),另一种像 phyrfox 所说的那样使任务异步(如果你可以的话,这很好用做),但最近三个通过强制执行页面响应的最短持续时间。我不确定我对此与方法二的看法,但无论如何我都会在下面详细说明:

您的情况的基本逻辑可能如下所示:

  1. 存储系统时间戳
  2. 从数据库加载用户
  3. 使用密码重置链接向用户发送电子邮件
  4. 检查现在与步骤 1 中的时间戳之间的时间差。如果差 x 小于最小持续时间
  5. 到目前为止,将响应完成延迟 x 减去经过的时间。

这有几个优点和几个缺点..

好的:

  • 其他一些较低级别的机制专注于让每个原子步骤花费相同的时间,在您的场景中,您需要确保所有步骤的总时间是相同的,这是一个全面的方法。
  • 这是一个快速简单的代码实现。

坏的:

  • 如果有人可以导致一个组件的延迟,它可以将时间延长到硬编码的最小数量之外。例如,如果平均往返时间为 1 秒,您可能会强制要求最短持续时间为 1.5 秒。如果数据库服务器响应时间由于良性或恶意活动而增加,它可能会将平均响应时间推至 2 秒,超过您的最短持续时间。您可以尝试使用动态最小持续时间来解决此问题,例如基于最近请求的平均持续时间(但具有设定的最小值)。
  • 您正在使您的系统更容易通过对具有这种机制的页面的多个请求而遭受 DOS。我最近看到使用此技巧的地方将这种风险视为两害相权取其轻。

编辑

由于我无法对其他答案发表评论,但我只想指出,在没有开放注册的系统上,您想尝试保持用户名的秘密,除非您的用户名在正常使用过程中被设计暴露系统——纵深防御人!为什么让它变得比需要的更容易?

就在我的脑海中:如果您可以快速枚举数千个用户,它会增加可能具有简单密码以进行暴力破解的帐户数量,或者您可能会将您的用户暴露于其他攻击媒介,例如鱼叉式网络钓鱼或社会工程学攻击。坏人对你的用户的信息越少越好。

任何类型的随机化只会减轻攻击,需要更多统计数据才能观察到时间差异。这不是一个好的解决方案,除非它是可以做到的最好的。

相反,我的建议是有一个发送电子邮件的后台进程。实际的恢复过程甚至不会检查用户名是否有效,它只会向后台进程(通过任何一种方便的IPC方法)发送一条消息,说发送用户名某某的电子邮件并立即返回“完成”页面。

后台进程将实际查看用户名是否有效,如果有效则发送电子邮件,但远程攻击者无法观察到其结果和时间。