CSRF 保护与 CORS 源头与 CSRF 令牌

此问题仅关于防止跨站点请求伪造攻击。

它具体是关于: 通过起源头(CORS)的保护是否与通过 CSRF 令牌的保护一样好?

例如:

  • 爱丽丝用浏览器登录到 https://example.com(使用 cookie)。我猜想,她使用的是现代浏览器。
  • Alice 访问 https://evil.example,evil.example 的客户端代码执行对 https://example.com的某种请求(经典的 CSRF 场景)。

所以:

  • 如果我们不检查 OriginHeader (服务器端) ,并且没有 CSRF 令牌,那么我们就有了一个 CSRF 安全漏洞。
  • 如果我们检查一个 CSRF 令牌,我们是安全的(但是这有点单调乏味)。
  • 如果我们确实检查了 OriginHeader,那么来自 evil.example 客户端代码的请求应该像使用 CSRF 令牌时一样被阻止——除非 evil.example 的代码以某种方式可以设置 OriginHeader。

我知道,如果我们相信 W3C 规范在所有现代浏览器中都能正确实现,那么 XHR (参见 跨来源资源共享的保安)就不可能做到这一点,至少不可能(可以吗?)

但其他类型的请求(例如表格提交)又如何呢?正在加载脚本/img/... 标记?或者页面可以用来(合法地)创建请求的任何其他方式?或者是某个 JS 黑客?

注意: 我不是在说

  • 本地应用程式,
  • 操纵浏览器,
  • 在 example.com 的页面上跨网站脚本错误,
  • ...
56239 次浏览

Web 内容不能篡改 OriginHeader。此外,在相同的起源策略下,一个起源甚至不能将自定义头发送到其他起源。[1]

因此,在 阻挡攻击中检查 OriginHeader 与使用 CSRF 令牌一样好。

依赖它的主要问题是它是否允许所有合法请求工作。提问者知道这个问题,并设置了问题来排除主要情况(没有旧的浏览器,只有 HTTPS)。

浏览器厂商遵循这些规则,但是插件呢?他们可能不会,但是这个问题忽略了“受操纵的浏览器”浏览器中让攻击者伪造 OriginHeader 的 bug 是怎么回事?也可能存在允许 CSRF 令牌跨源泄漏的错误,因此需要做更多的工作来证明其中一个比另一个更好。

如果我们相信 W3C 规范在所有现代浏览器中都能正确实现,那么 XHR 就不可能做到这一点(参见安全跨来源资源共享) ,至少不可能(我们可以吗?)

最后,您必须“信任”客户端浏览器来安全地存储用户的数据,并保护会话的客户端。如果你不信任客户端浏览器,那么除了静态内容,你应该停止使用 web。即使使用 CSRF 令牌,您也相信客户端浏览器能够正确地遵守 同一原产地政策

虽然以前也有过浏览器漏洞,比如在 IE 5.5/6.0中,攻击者可以绕过相同的起源策略执行攻击,但是通常情况下,一旦发现这些漏洞,大多数浏览器都会自动更新,这种风险将大大降低。

但其他类型的请求(例如表格提交)又如何呢?正在加载脚本/img/... 标记?或者页面可以用来(合法地)创建请求的任何其他方式?或者是某个 JS 黑客?

Origin报头通常只发送给 XHR 跨域请求。图像请求不包含报头。

注意: 我不是在说

  • 本地应用程式,

  • 操纵浏览器,

  • 在 example.com 的页面上跨网站脚本错误,

我不确定这是否属于受操纵的浏览器,但是 旧版本的 Flash允许设置任意的头,使攻击者能够从受害者的机器发送带有欺骗的 referer头的请求来执行攻击。

解释术语

CORS (跨来源资源共享)是一个 规定,它允许两个不同的域相互通信。CORS (起飞前)请求需要在头部包含 Origin属性。CORS 飞行前请求的响应在头部包含 Access-Control-Allow-Origin属性,以允许/限制来自不同域的访问。(图片的好例子可以找到 在 MDN 网页文件)。

除了 GETHEAD请求之外,属性 起源在每个请求头中。CORS (预起飞)请求必须包含 Origin属性,但不是每个具有 Origin属性的请求都是 CORS (预起飞)请求

同一原产地政策是一种限制域之间通信的机制。

CSRF 令牌(Cross-Site-Request-Forgery)存储在用户的会话中,必须与 POST/DELETE/PUT 请求一起发送。在服务器端,CSRF 令牌将与会话中的值进行比较,并且只有在它们匹配时才允许继续。

同源策略阻止跨域的通信

所有浏览器都实现了 同一原产地政策。此策略通常可以避免域 A 上的 Web 应用程序可以向域 B 上的应用程序发出 HTTP 请求,但是它并不限制所有请求。例如,同源策略的 不限制嵌入式标记是这样的:

<img src="https://dashboard.example.com/post-message/hello">

响应是否是有效的映像无关紧要ーー请求仍然被执行。这就是为什么不能用 GET 方法调用 Web 应用程序上的状态更改端点非常重要的原因。

飞行前检查

您可以使用 CORS来避免同源策略,并允许域 A 向域 B 发出请求,否则这将是被禁止的。 在实际发送请求之前,将发送一个 飞行前请求来检查服务器是否允许域 A 发送此请求类型。如果是这样,域 B 将在响应头中有 Access-Control-Allow-Origin字段,域 A 将发送实际请求。

例如,如果没有设置 Access-Control-Allow-Origin,那么对于域 A,Javascript XMLHttpRequest 将通过起飞前限制,而不在域 B 上执行请求。

为什么需要 CSRF 令牌而不需要同源策略

如果同源策略适用于所有类型的请求,那么就不需要使用 CSRF 令牌,因为可以通过同源策略得到完全保护,并且可以使用 CORS 来明确说明哪个域可以通信。然而,事实并非如此。有几个 HTTP 请求没有发送 CORS 飞行前请求

带有特定标头和特定内容类型的 GET、 HEAD 和 POST 请求不会发送飞行前请求。这样的请求称为 简单的要求。这意味着将执行请求,如果不允许请求,则将返回不允许的错误响应。但问题是,这个简单的请求是在服务器上执行的。

由于表单在 CORS之前就已经存在,而且在那里已经允许向任何来源发送东西,人们不想用飞行前对 POST请求的请求来破坏现有的互联网。另见 给你:

其动机是 HTML 4.0中的元素(早于跨站点的 XMLHttpRequest 和 get)可以向任何来源提交简单的请求,因此任何编写服务器的人都必须已经在防止跨站请求伪造(CSRF)。在这种假设下,服务器不必选择加入(通过响应飞行前请求)来接收任何看起来像表单提交的请求,因为 CSRF 的威胁并不比表单提交的威胁更严重。但是,服务器仍然必须选择使用 Access-Control-allow-Origin 与脚本共享响应。

不幸的是,一个简单的 <form action="POST">会创建一个简单的请求!

由于这些简单的请求,我们必须使用 CSRF 令牌保护 POST 路由(GET 路由不需要 CSRF,因为它们可以通过嵌入式标记读取,如上所示。只要确保没有状态更改的 get 方法)。

检查请求的来源是否足够?

由于 POST 请求在头部发送原点并且浏览器不允许修改它,因此人们可能想知道在没有飞行前检查的情况下,仅仅检查原点是否足够。但是,在 CLI 上执行的脚本可以将原点设置为任意值。此外,由于用户可能拥有的插件组合,还是有可能改变原点。另一个问题是浏览器可能无法支持它(Edge 在2015年4月发布,但在2015年6月支持原始头部,Firefox 中的 bug 在2008 https://bugzilla.mozilla.org/show_bug.cgi?id=446344中得到了修复)。

来自 维基百科:

各种其他技术已经被用于或提议用于 CSRF 验证请求的头部是否包含 [ ... ] HTTP Origin 头部。然而,这是不安全的-一个组合 浏览器插件和重定向可以让攻击者提供 在对任何网站的请求上自定义 HTTP 标头,因此允许 伪造的请求

因此,建议使用 CSRF 令牌,而不是依赖于原始头。但在某些情况下,由于会话过期,不使用 CSRF 令牌可能更好。

CSRF 令牌的问题

我会避免使用 CSRF 令牌作为联系表单。因为它可能会导致这样一种情况,即用户写了很长时间,当他提交时,会话已经过时,CSRF 令牌无效,并且用户的整个消息被删除。那将是一种糟糕的用户体验。 对于联系表单,您希望避免从 SPAM 获得消息,因此 重演可能是合理的选择,因为它不需要用户交互。

您还可以增加浏览器上会话的生命周期,但这可能会导致存储空间过大。或者,您可以使用存储在客户端的 Cookie 散列会话。这些会话也不应该过期,即使客户端必须在提交表单之前休息12个小时。

我的总结

  • 对关键端点使用 CRSF 令牌,如登录或商店结帐
  • 出于实际原因,我建议不要将 CSRF 令牌用于申请表或联系表。
  • 永远不要使用状态更改的 GET 端点