如何使用跨源请求 (CORS) 实现 CSRF 保护

信息安全 Web应用程序 饼干 csrf 科尔斯 跨域
2021-08-29 00:32:00

tl; dr Cookie-to-header-token 方法无法工作,因为客户端无法以任何方式读取 CSRF 令牌 cookie。是否在标头中发送令牌,并让客户端立即将其保存在 cookie 中被认为是有效的替代方案?


我试图在客户端存储所有静态资产(包括 index.html 入口点)并且服务器位于完全不同的域(无共享父域)中的跨域场景中找到一种缓解 CSRF 的方法。

我首先查看的是Cookie to Header Token方法,但似乎客户端和服务器都必须位于同一来源。

(服务器发出一个名为 XSRF-TOKEN 的 JavaScript 可读 cookie,位于同一来源的客户端可以读取 cookie,然后在所有后续调用中添加标头,例如 X-XSRF-TOKEN,例如 Angular 的处理方式CSRF,只要两者都在同一个域上或共享某个父域,这一切都很好)

但是,对于跨域域,这似乎行不通。

假设

  1. XHR 无法访问Set-Cookie标头XHR 不允许查看响应 cookie。即使使用最宽松的 CORS 标头,即使未设置为 httpOnly,它也会被浏览器阻止。(如果查看开发工具,它也会隐藏 Set-Cookie 标头)(https://fetch.spec.whatwg.org/#forbidden-response-header-name

  2. 显然,其他域的 cookie 不是 JavaScript 可访问的,即使没有设置为 httpOnly,并且即使 XHR 请求是withCredentials=true并且将在将来的请求中将该 cookie 发送到 cookie 的域(假设服务器中的匹配标头),它也永远无法通过 JavaScript 访问,并且我们都对此表示感谢。

  3. 将 Cookie 域设置为 XHR 的 Origin 不起作用如果服务器将 cookie 设置为客户端域怎么办?当然,不可能为不同的域设置 cookie(https://www.rfc-editor.org/rfc/rfc6265#section-4.1.2.3),这包括 XHR,例如,如果我在页面ui.example.com制作中有 XHR对服务器的请求api.example.net,并且服务器设置了一个带有域的cookie ui.example.com,用户代理似乎拒绝了我所看到的cookie,(我假设因为服务器和页面本身仍然处于不同的来源),虽然XHR与 cookie 中设置Origin的相同,Domain这是行不通的,我相信它有充分的理由。

目前我能想到的唯一方法是让服务器将 CSRF 令牌作为 XHR 可以读取的标头发送,然后客户端将通过 JavaScript 将其存储在客户端域范围的 cookie 中,然后其余的将像在上面的 Cookie 到 Header Token方法。

问题

  1. 我上面的任何假设都不正确吗?

  2. 是否有任何权威来源将上述替代方法定义为安全?与同域 cookie-to-header-token 方法相比,它是否有明显的缺点?

  3. 有没有其他方法可以安全地缓解这个用例的 CSRF?

我审查的相关答案

我经历了以下问题/答案,但没有一个感觉像是对我的问题的权威答案,我可能错过了字里行间的阅读,所以如果这是完全重复的,我深表歉意

1个回答

我们的谈话从评论部分开始,但我意识到我写的内容有些不准确。跨域共享 cookie 比我最初想象的要严格。这应该是一个更全面的概述,并且更接近您可能正在寻找的内容。

我会做几个假设:

  • 您有两个域:foo.com(ui) 和bar.com(api)
  • 您想防止另一个域,例如从(CSRF + CORS 保护)evil.com引起副作用/读取响应bar.com

方法 1:使用 CSRF 令牌

这可以使用 cookie 来完成,或者简单地使用自定义标头并将值存储在会话存储中或作为表单中的隐藏输入。这意味着您需要手动将 CSRF 令牌作为自定义标头与每个请求一起发送。来自服务器和客户端。

Cookie 在这里不是一个好的选择,因为foo.combar.com是单独的域。

不同域的 cookie 限制(不是同一域的子域)

foo.com --(请求)--> bar.com
  • 存储在您的浏览器中的任何 cookie Domain=bar.com(不包括SameSite=laxSameSite=strict)将与请求一起发送,假设withCredentials=true
foo.com <--(响应)-- bar.com bar.com --(请求)--> bar.com
  • 在这里,Set-Cookie将工作。因此,来自foo.com->bar.com然后返回的重定向将能够使用Domain=bar.com. (所以这可能是一个有点丑陋/缓慢的解决方法)

方法 2:检查 Origin 标头

这可能是最简单/最干净的选择。(如果您使用此方法,则不需要 CSRF 令牌)。

注意:OWASP 建议仅使用 Origin 标头作为 CSRF 令牌的辅助措施的原因是 Origin 标头在提出建议时还不支持所有常见的浏览器。很长一段时间以来,所有常见的浏览器都支持此功能。(目前~3-4岁)

*.com--(请求)--> bar.com (CSRF)

确保检查-serverOrigin中传入请求的标头。bar.com如果缺少 或https://foo.com,则应接受该请求。否则,响应应该类似于403 (Unauthorized)

*.com<--(响应)-- bar.com (CORS)

响应需要具有正确的 CORS 标头(假设它已被接受,并且 Origin 标头没有丢失):

// Something like this, depending on your server language
response.setHeader('Access-Control-Allow-Origin', request.getHeader('Origin'))