为什么 Rails 中的 CSRF 令牌不能阻止多个选项卡正常工作?

Why does the CSRF token in Rails not prevent multiple tabs from working properly?

在阅读 Rails 中 CSRF 保护的工作原理后,我尝试通过以下方式触发 CSRF 保护:

注意:我们使用的是基于 cookie 的会话。

  1. 访问登录页面。检查 meta 中的 CSRF 令牌 => abc123
  2. 打开第二个浏览器选项卡,访问相同的登录页面。元中的 CSRF 令牌不同 => def456
  3. 返回第一个标签。
  4. 提交登录凭据。

我预计这会失败,因为第二个选项卡生成了一个新的、不同的 CSRF 令牌。当登录表单提交时,提交给服务器的令牌不应该是一个陈旧的令牌吗?

但是,确实有效:

  1. 访问登录页面。检查 meta 中的 CSRF 令牌 => abc123
  2. 打开第二个浏览器选项卡,访问相同的登录页面。元中的 CSRF 令牌不同 => def456
  3. 返回第一个标签。
  4. 提交登录凭据。
  5. 注销(清除会话)
  6. 转到第二个选项卡,然后提交登录信息。

在这种情况下,我得到了预期的 InvalidAuthenticityToken 异常。为什么?

来源:https://medium.com/rubyinside/a-deep-dive-into-csrf-protection-in-rails-19fa0a42c0ef

为什么第二个选项卡中的请求不会失败

meta 标签中的 CSRF 令牌实际上是两个字符串的串联:每个请求生成的 "one-time pad",以及 "real" CSRF 秘密与 one-time 软垫。在下图中查看 one-time pad 如何添加到屏蔽标记中的 XORed 字符串之前,该标记存储在 meta 标记中:

Rails 将 CSRF 秘密存储在 session cookie 中,无需异或。 Javascript 应在浏览器中使用,以从 meta 标记中读取屏蔽标记并将其传递到 X-CSRF-TOKEN header.

当 Rails 验证请求时,它:

  1. 拆分 X-CSRF-TOKEN header 中传递的值以检索 one-time 填充和异或字符串。
  2. 将它们异或在一起以检索真正的秘密。
  3. 将其与 cookie 中的秘密进行比较。

这就是您在 meta 标签中看到不断变化的标记的原因 -- one-time 垫不同。如果您验证了令牌,您会在两个令牌中发现相同的秘密。

注:这one-time垫业务似乎没有必要。任何人只要拥有被屏蔽的令牌就可以取回真正的秘密。令人惊讶的是,XORing 的目的是在每次请求时更改 CSRF 令牌,这样攻击者就无法使用定时攻击来辨别秘密。参见 this paper on the BREACH SSL attack

为什么注销时请求失败

@max's comment 中所述,注销会删除 session cookie。下一个请求生成一个新的 CSRF 秘密,它不再匹配旧的屏蔽令牌。