我应该担心对字符串比较的远程定时攻击吗?

信息安全 爪哇 定时攻击 侧信道
2021-09-04 21:07:08

假设我们有一个 Java Web 应用程序,它使用共享密钥来验证客户端的身份。秘密存储在服务器上,客户端通过 SSL 传输秘密并对其进行检查:

String SECRET_ON_SERVER = "SomeLongRandomValue";

if (secretFromClient.equals(SECRET_ON_SERVER)) {
    // request verified - client knows the secret
} else {
    // request not verified - generate failed response
}

String.equals(String)一旦单个字符不匹配就返回。这意味着,如果攻击者能够准确跟踪响应所需的时间,理论上应该知道他们的尝试有多少字符secretFromClient- 匹配服务器机密,从而导致看似合理的暴力攻击。

时间上的差异似乎很小快速调查表明差异很容易在亚毫秒范围内。

  • 我可以安全地忽略这些类型的定时攻击,因为它与网络噪声相比微不足道吗?
  • 是否有通过 Internet 成功进行 < 1ms 定时攻击的示例?
4个回答

远程定时攻击已发表成功从文档中—— “......我们可以可靠地区分低至 20µs 的远程定时差异。” 所以是的,你应该担心.equals()(剧透:不安全)的底层实现。.equals()使用字符的 XOR 之和以与时间无关的方式进行比较来实现

这是一个 python 实现,作为与时间无关的字节比较的示例。

def equals(bytes1, bytes2):
    if len(bytes1) != len(bytes2):
        return False
    else:
        differences = 0
        for a, b in zip(bytes1, bytes2):
            differences |= a ^ b
        return differences == 0

从理论上讲,这是一个可能的漏洞,如果您处于超级偏执模式,您应该假设答案是“是”。在所有其他情况下,答案都是:“不”。.

尽管有已发表的论文(其中一篇链接在@Oasiscircle 的答案中)声称它们能够成功运行定时攻击,但也必须仔细阅读前提条件。这些已发布的“实用”攻击适用于LAN 上的某些算法,其间最多有两个交换机。这意味着几乎完全可靠、恒定的往返时间。对于这种情况,通过时序攻击某些算法确实是可行的,但这在问题的上下文中是没有意义的。 事实上,我认为这些远程攻击是“作弊”如果您仔细设计实验,那么远程攻击这一事实是无关紧要的,因此延迟几乎是完全可以预测的。

当攻击互联网上的任何服务器时,这个前提条件不成立(甚至远程,双关语),即使在地理和拓扑上接近的服务器上也是如此。

此外,通过计时攻击字符串比较与攻击 RSA 计算完全不同。这要困难得多,因为整个操作以及可测量的差异要小得多。

密码的字符串比较(假设您的密码“合理”大小)需要几百个周期或更短的时间,其中可能的初始缓存/TLB 未命中是迄今为止最大的主导因素,其次是终端错误预测的分支(匹配和不匹配都会发生)。匹配和不匹配之间的差异可能是一到两打纳秒。

上下文切换需要数百纳秒,缓存未命中也是如此。调度程序通常以微秒或毫秒的分辨率运行,并在至少可以说很难预测的时间之间做一些非常重要的工作(在数百/数千纳秒内)。

可靠测量的纳秒级规模的差异并不完全是微不足道的,无论是。普通的可编程定时器几乎没有所需的分辨率。商用硬件上的 HPET 保证提供 100ns 的分辨率(每个规格),实际上在许多实现中降至 1ns。但是,它通过产生中断来工作这意味着您可以将计时器安排到精确到纳秒的某个时间点,但您不能真正使用它来测量单个纳秒。此外,中断增加了几十纳秒(......到几十纳秒)的开销和不确定性你想测量的!)。循环计数器需要序列化才能准确。这也使得它们对于以纳秒分辨率精确测量外部事件相当无用,因为它们的准确性取决于管道的外观。
还有更多的事情需要考虑,它们会增加不可预测的噪音,例如合法用户(是的,那些也存在!)和中断合并。

试图从包括几个不同纳米的东西以及一些微的东西和几个的东西的样本中预测一些东西纳米是一项艰巨的任务。这是来自各个规模的几个独立来源的噪音。

最后,考虑提到“Java”,这意味着例如垃圾收集器可能在不可预知的时间执行(无论如何,对于远程攻击者来说是不可预知的),导致未知(微,毫?)规模的不可预知的抖动。

理论上,您当然可以收集大量样本,即使是在较低分辨率下(例如微秒级),并在统计上消除各种噪声源。您永远无法绝对确定密码是否正确,但您最终将能够以足够高的概率(例如 85% 或 90%,甚至 99%)进行判断,然后您可以手动验证这些少数候选人。这已经足够了!

这是可能的,至少在理论上是这样,但即使是对单个密码的预测也需要大量的样本。说“巨大”确实是对银河系比例的轻描淡写。所需的样本数量实际上意味着您必须并行化攻击,否则将永远持续下去。

现在,将这种定时攻击并行化到任何严重程度都不容易,因为你会受到观察者效应的影响(与量子力学的意义相同)。
假设服务器有足够的空闲内核,并行执行几个探测(可能 5-8 个)应该可以工作,但是随着您的扩展,最终一个探测将不可避免地以不可预测和不成比例的方式影响另一个探测的结果。您无法采取任何措施来防止这种情况发生,因此并行化并不能很好地工作(我什至没有考虑到中断通常通过单个内核并且只有一根物理铜线可以传输数据必须通过,因此即使服务器仍有空闲内核剩余,它也可能 很可能是一个探针影响另一个探针的情况)。

另一方面,运行非大规模并行攻击必然会失败,因为在找到一个密码之前你就会老死。

在服务器上存储一个好的密码散列(即把它当作密码对待)。然后,您的比较将是获取客户端发送给您的字符串的哈希值,然后比较哈希值。

如果秘密具有足够高的熵,这应该消除定时攻击防止真正的秘密字符串泄漏,因为实际上不可能从哈希中恢复秘密。

另一方面,如果秘密中的熵不足以防止字典攻击,仅此是不够的。提前退出比较仍然可以让攻击者了解散列的前几个字节;那么随后的字典攻击可能能够从其哈希中恢复秘密。(有关此类定时攻击可能性的更多讨论,另请参见密码哈希的定时攻击。)这可以通过使用恒定时间比较方法比较两个哈希来防止。

因此,最可靠的解决方案是存储密钥的哈希值,对客户端发送给您的字符串进行哈希处理,然后使用安全的恒定时间比较方法比较两个哈希值。使用盐渍哈希也不会受到伤害。

@Ben 直接比较哈希而不是键的答案似乎是该任务的最佳实践方法,并且过路人也成为该问题的部分解决方案

但是,它仍然容易受到某种程度的彩虹表哈希树的影响:尝试产生以每个字母开头的哈希的键,然后是那些以找到的字母开头并循环第二个字母的键,依此类推。盐和胡椒可能会以某种方式解决这个问题,但如果这是一个时间问题,那么更好的解决方案是完全防止时间攻击,在比较之后休眠直到下一个 100 毫秒的倍数(或其他任何时间)时间明显大于 strcmp 可以)。

但是,要回答所提出的问题,我们需要计算风险有多大。

在 1GHz 机器上,一个时钟周期是一纳秒。字符串比较的单个字符应按此顺序进行,任何 CPU 缓存命中也应按此顺序进行(L1 约为 1ns,L3 约为 30ns)。据我了解,大多数字符串比较函数不会从内存中逐一获取字母,而是以最有效的权衡块大小获取它们。即使他们这样做了,在最坏的情况下,需要 DRAM 访问的缓存未命中仍然只有大约 100ns/字符。

@Oasiscircle 链接到一篇文章,引用他们 20us 的 WAN 时间。

但是,确定机器的托管位置是微不足道的。在同一个 NOC 或服务器场中租用机器的人会知道您的确切硬件配置,并且您之间可能只有一两个交换机。如果他们租用两台服务器,他们甚至可以建立一个测试系统来测试通过该 NOC 的测量时间,以尽可能接近他们的结果。

因此,该论文可能更令人感兴趣的是他们的 LAN 时序为 100ns,2,000 次测量,错误率 <5%(或 200ns,1,000 次测量:更多测量将提供更好的分辨率)。他们没有描述他们的网络硬件,但很可能如果攻击机和目标机上都有千兆卡,他们可能会得到更好的分辨率。

届时,攻击者将能够将长度区分为最近的 1 到 100 个字符之间的某个位置(取决于您的缓存命中情况、它们运行的​​测试数量、网络速度以及过多的硬件细点)。

因为应该假设存在可以保证缓存未命中的攻击,他们可以运行任意数量的测试,他们会选择最适合攻击你的硬件,并且他们的攻击解决方案只会变得更好您的应用程序和服务器的生命周期...有效地为它们提供了 1 个分辨率字符,从而为您提供了整个密钥。

因此,如果您的系统可能有技术先进的攻击者愿意花钱专门针对您的机器,那么这是一个明显的风险,因为他们几乎可以肯定地使用这种方法取得成功。

同样,如果您在 NOC 中租用空间,即使是来自其他 NOC 占用者的路过式攻击也是一种风险,至少如果这种比较是使用路过式攻击可能发现的协议进行的。

简短的回答:是的,这是一种风险;防止操作的时间受到 strcmp 的影响是微不足道的;最好这样做。