生成抗CRIME攻击的安全CSRF令牌技术解析

本文深入探讨如何生成安全的CSRF令牌以抵御TLS压缩攻击(如CRIME和BREACH),提出结合随机值和哈希函数的令牌设计方案,并分析SameSite Cookies的防护效果与局限性。

生成抗CRIME攻击的安全CSRF令牌

在最近的一个小型Web项目中,我不得不思考如何生成安全令牌来防止跨站请求伪造(CSRF)。我想分享我认为应该如何实现这一点,主要是为了获得一些反馈,看看其他人是否同意或认为有改进的空间。

这里我不会一般性地讨论CSRF,我通常假设您了解这种攻击类别的工作原理。防止CSRF的标准方法是在每个执行操作的表单中添加一个令牌,该令牌对于会话来说是足够随机和唯一的。

一些Web应用程序对每个请求使用相同的令牌,或者至少对同一类型的每个请求使用相同的令牌。然而,由于某些TLS攻击,这是有问题的。

有几种针对TLS和HTTPS的攻击,通过生成大量请求然后慢慢了解秘密来工作。这类攻击的一个常见目标是CSRF令牌。这些攻击的列表很长:BEAST、所有填充Oracle攻击(Lucky Thirteen、POODLE、Zombie POODLE、GOLDENDOODLE)、RC4偏置攻击,可能还有更多我忘记的攻击。好消息是,这些攻击都不应该成为问题,因为它们都影响了脆弱的密码学,这些密码学在现代TLS堆栈中已不存在。

然而,有一类TLS攻击仍然值得关注,因为没有好的通用修复方法,这些是基于压缩的攻击。第一个被展示的此类攻击称为CRIME,它针对TLS压缩。TLS压缩不再使用,但后来的一个称为BREACH的攻击使用了HTTP压缩,这仍然广泛使用,并且没有人想禁用它,因为HTML代码压缩得非常好。这些攻击的进一步改进被称为TIME和HEIST。

我不会详细讨论这些攻击,只需知道它们都依赖于一个秘密在连接上反复传输。因此,如果CSRF令牌在多个连接中相同,它们就容易受到这种攻击。如果我们有一个始终变化的CSRF令牌,这种攻击就不适用于它。

一个明显的修复方法是始终生成新的CSRF令牌。然而,这需要在服务器上进行相当多的状态管理或其他权衡,因此我认为这不是可取的。更好的概念是保持一个单一的服务器端秘密,但加入一些随机性,以便令牌在每个请求上变化。

BREACH的作者有以下简要建议(感谢Ivan Ristic指出这一点):“掩码秘密(通过每个请求与随机秘密进行XOR操作来有效随机化)”。

我将其理解为有一个真实令牌和一个随机值,CSRF令牌看起来像random_value + XOR(random_value, real_token)。服务器可以通过拆分令牌,将前半部分与后半部分进行XOR操作,然后与真实令牌比较来验证。

但我想补充一点:我希望用于一个表单和操作的令牌不能用于另一个操作。如果存在任何形式的令牌外泄,尽可能限制令牌的效用似乎是合理的。

因此,我的想法是使用密码学哈希函数而不是XOR,并添加一个范围字符串。这可以是“adduser”、“addblogpost”等,任何标识操作的东西。

服务器将在服务器端为每个会话保留一个秘密令牌,CSRF令牌将如下所示:random_value + hash(random_value + secret_token + scope)。每次发送令牌时,随机值都会变化。

我创建了一些简单的PHP代码来实现这一点(如果有足够的兴趣,我将学习如何将其转换为composer包)。用法非常简单,有一个函数用于创建令牌,该函数以范围字符串作为唯一参数,另一个函数用于检查令牌,该函数以公共令牌和范围作为参数,并返回true或false。

至于实现细节,我使用256位随机值和秘密令牌,这过于多了,应该避免任何关于它们太短的讨论。对于哈希,我使用sha364,它被广泛支持且不易受到长度扩展攻击。我不认为长度扩展攻击在这里有任何相关性,但这感觉更安全。我相信哈希输入的顺序不应该重要,但我见过一些结构,其中…

CSRF令牌是Base64编码的,这在HTML表单中应该工作良好。

我的问题是,人们是否认为这是一个合理的设计,或者他们是否看到改进的空间。另外,由于这一切都相对直接和明显,我几乎可以肯定我不是第一个发明这个的人,欢迎指点。

现在有一个房间里的大象我也需要讨论。表单令牌是防止CSRF攻击的传统方式,但近年来浏览器引入了一种全新且完全不同的防止CSRF攻击的方式,称为SameSite Cookies。长期计划是默认启用它们,这可能会使CSRF几乎不可能。(这些计划由于Covid-19而延迟,并且可能会有一些不幸的兼容性权衡,这些权衡足以在SameSite-by-default的世界中仍然手动设置标志。)

SameSite Cookies有两种风格:Lax和Strict。当设置为Lax时,我建议每个Web应用程序都应该这样做,从另一个主机发送的POST请求会在没有Cookie的情况下发送。使用Strict时,所有请求,包括GET请求,从另一个主机发送时都会在没有Cookie的情况下发送。我认为在大多数情况下,这不是一个可取的设置,因为这破坏了许多工作流程,并且GET请求不应该执行任何操作。

现在我的问题是:SameSite cookies足够吗?我们甚至还需要担心CSRF令牌吗,或者我们可以跳过它们?有没有任何场景可以绕过SameSite Cookies,但不能绕过CSRF令牌?

当然,有人可以说“为什么不两者都做?”并将其视为一种深度防御。这是一种流行的思维方式,总是认为更多的安全机制更好,但我不同意这种推理。安全机制引入复杂性,如果你可以用更少的复杂性完成,通常应该这样做。CSRF令牌对我来说总感觉是一个丑陋的解决方案,我觉得SameSite Cookies是解决这个问题的更干净的方式。

那么,有没有SameSite Cookies不保护而我们还需要令牌的情况?明显的一个是不支持SameSite Cookies的旧浏览器,但它们已经存在一段时间了,如果你愿意不支持真正古老和晦涩的浏览器,那应该不重要。

我能想到的一个剩余问题是接受操作请求作为GET和POST变量的软件(例如,在PHP中,如果使用$_REQUESTS变量而不是$_POST)。这些需要避免,但无论如何不应该使用GET来执行应用程序中的任何操作。(SameSite=Strict并没有真正修复这个问题,因为GET请求仍然可能来自链接,例如在支持内部消息传递的应用程序上。)

另外,一个边缘情况问题可能是过渡期:如果一个Web应用程序同时移除CSRF令牌并开始使用SameSite Cookies,用户可能仍然有旧的没有标志的Cookies。因此,应该使用至少与Cookie生命周期一样长的过渡期。

此外,浏览器供应商计划的SameSite-by-default Cookies存在绕过,但这些不适用于Web应用程序自己设置SameSite标志的情况。(基本上,SameSite-by-default Cookies只在两分钟后才是SameSite,因此在设置Cookie后有一个小的攻击窗口。)

考虑到所有这些,如果一个人仔细确保操作只能通过POST请求执行,在所有Cookies上设置SameSite=Lax,并计划一个过渡期,应该能够安全地移除CSRF令牌。有人不同意吗?

图片来源:Pixsels, Wikimedia Commons

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计