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

本文探讨了如何生成安全的CSRF令牌以抵御CRIME等TLS压缩攻击,提出了结合随机值和哈希函数的令牌设计方案,并讨论了SameSite Cookie作为替代方案的优缺点及适用场景。

生成抗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 Cookie。长期计划是默认启用它们,这可能会使CSRF几乎不可能。(这些计划由于Covid-19而延迟,并且可能会有一些不幸的兼容性权衡,这足以在SameSite-by-default的世界中手动设置标志。)

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

现在我的问题是:SameSite cookie足够吗?我们甚至还需要担心CSRF令牌吗,或者我们可以跳过它们?是否存在可以绕过SameSite Cookie但无法绕过CSRF令牌的场景?

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

那么,是否存在SameSite Cookie无法保护而我们需要令牌的情况?明显的一个是不支持SameSite Cookie的旧浏览器,但它们已经存在一段时间了,如果你愿意不支持非常旧和晦涩的浏览器,那应该不重要。

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

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

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

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

图片来源:Pixsels, Wikimedia Commons

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