453

跨域资源共享是一种允许网页向另一个域(来自Wikipedia)发出 XMLHttpRequests 的机制。

在过去的几天里,我一直在摆弄 CORS,我想我对一切是如何运作的有了很好的理解。

所以我的问题不是关于 CORS / preflight 如何工作,而是关于将 preflights 作为新请求类型提出的原因。我看不出为什么服务器 A 需要向服务器 B 发送预检 (PR) 只是为了确定是否会接受真正的请求 (RR) - B 肯定有可能接受/拒绝 RR 而无需任何先前的 PR。

经过一番搜索后,我在www.w3.org (7.1.5)找到了这条信息:

为了保护资源免受在本规范存在之前无法源自某些用户代理的跨域请求,发出预检请求以确保资源了解本规范。

我发现这是有史以来最难理解的句子。我的解释(最好称其为“最佳猜测”)是关于保护服务器 B 免受来自不了解规范的服务器 C 的请求。

有人可以解释一个场景/展示一个 PR + RR 比单独使用 RR 解决的问题吗?

4

9 回答 9

377

我花了一些时间对预检请求的目的感到困惑,但我想我现在明白了。

关键的见解是预检请求不是安全问题。相反,它们是不改变规则的东西。

预检请求与安全无关,它们与现在正在开发的应用程序无关,具有 CORS 意识。相反,预检机制有益于在没有意识到 CORS 的情况下开发的服务器,并且它充当客户端和服务器之间的健全性检查,它们都支持 CORS。CORS 的开发人员认为那里有足够多的服务器依赖于他们永远不会收到的假设,例如,他们发明了预检机制以允许双方选择加入的跨域删除请求。他们认为,如果只是简单地启用跨域调用,就会破坏太多现有的应用程序。

这里有三种情况:

  1. 旧服务器,不再在开发中,在 CORS 之前开发。这些服务器可能会假设它们永远不会收到例如跨域删除请求。这种情况是预检机制的主要受益者。是的,这些服务可能已经被恶意或不合格的用户代理滥用(而 CORS 并没有改变这一点),但在使用 CORS 的世界中,预检机制提供了额外的“健全性检查”,因此客户端和服务器不会打破,因为网络的基本规则已经改变。

  2. 仍在开发中但包含大量旧代码的服务器,并且不可行/不希望审核所有旧代码以确保其在跨域世界中正常工作。这种情况允许服务器逐步选择加入 CORS,例如通过说“现在我将允许这个特定的标头”、“现在我将允许这个特定的 HTTP 动词”、“现在我将允许 cookie/auth 信息发送”等。这种情况得益于预检机制。

  3. 以 CORS 意识编写的新服务器。根据标准安全实践,服务器必须在面对任何传入请求时保护其资源——服务器不能相信客户端不会做恶意事情。这种情况不会从预检机制中受益:预检机制不会为已正确保护其资源的服务器带来额外的安全性。

于 2013-06-05T16:36:40.260 回答
307

引入预检请求的动机是什么?

引入了预检请求,以便浏览器在发送某些请求之前可以确定它正在处理可识别 CORS 的服务器。这些请求被定义为具有潜在危险(状态改变)和新的(在 CORS 之前由于Same Origin Policy是不可能的)的请求。使用预检请求意味着服务器必须选择加入(通过正确响应预检)来接受 CORS 实现的新的、具有潜在危险的请求类型。

这就是原始规范这部分的含义:“为了保护资源免受在本规范存在之前无法源自某些用户代理的跨域请求,发出预检请求以确保资源了解本规范。”

你可以给我一个例子吗?

让我们假设一个浏览器用户登录到他们的银行网站A.com。当他们导航到恶意页面时B.com,该页面包含一些试图向其发送DELETE请求的Javascript A.com/account。由于用户已登录A.com,因此该请求(如果发送)将包含识别用户的 cookie。

在 CORS 之前,浏览器的同源策略会阻止它发送此请求。但既然 CORS 的目的就是让这种跨域通信成为可能,那已经不合适了。

浏览器可以简单地发送DELETE并让服务器决定如何处理它。但是如果A.com不知道 CORS 协议怎么办?它可能会继续执行危险的操作DELETE。它可能会假设——由于浏览器的同源策略——它永远不会收到这样的请求,因此它可能永远不会针对这种攻击进行强化。

为了保护这种不支持 CORS 的服务器,协议要求浏览器首先发送预检请求。这种新类型的请求只有 CORS 感知服务器才能正确响应,允许浏览器知道发送实际的DELETE.

为什么对浏览器这么大惊小怪,攻击者不能只DELETE从自己的计算机发送请求吗?

当然可以,但是这样的请求不会包含用户的 cookie。旨在防止的攻击依赖于浏览器将与请求一起发送其他域的 cookie(特别是用户的身份验证信息)这一事实。

这听起来像Cross-Site Request Forgery,其中网站上的表单B.com可以A.com与用户的 cookie 一起提交并造成损害。

这是正确的。另一种说法是,创建预检请求是为了不增加非 CORS 感知服务器的 CSRF 攻击面。

但是POST列为不需要预检的方法。这可以像DELETE!

确实如此!CORS 不会保护您的站点免受 CSRF 攻击。再说一次,没有 CORS,您也无法免受 CSRF 攻击。预检请求的目的只是将您的 CSRF 暴露于 CORS 之前世界中已经存在的内容。

叹。好的,我勉强接受了预检请求的需要。但是为什么我们必须对服务器上的每个资源(URL)都这样做呢?服务器要么处理 CORS,要么不处理。

你确定吗?多个服务器处理单个域的请求并不少见。例如,请求可能A.com/url1由一种服务器处理,而请求可能由另一种服务器A.com/url2处理。通常情况下,处理单个资源的服务器不能对该域上的所有资源进行安全保证。

美好的。让我们妥协。让我们创建一个新的 CORS 标头,允许服务器准确说明它可以代表哪些资源,这样就可以避免对这些 URL 的额外预检请求。

好主意!事实上,标题Access-Control-Policy-Path就是为了这个目的而提出的。但最终,它被排除在规范之外,显然是因为某些服务器错误地实现了 URI 规范,使得对浏览器似乎安全的路径的请求在损坏的服务器上实际上并不安全。

这是一个审慎的决定,将安全性置于性能之上,允许浏览器立即实施 CORS 规范,而不会使现有服务器面临风险?还是仅仅为了在特定时间容纳特定服务器中的错误而注定互联网浪费带宽和加倍延迟是短视的?

意见不一。

好吧,至少浏览器会缓存单个 URL 的预检?

是的。虽然可能不会持续很长时间。在 WebKit 浏览器中,最大预检缓存时间当前为 10 分钟

叹。好吧,如果我知道我的服务器支持 CORS,因此不需要预检请求提供的保护,我有什么办法可以避免它们吗?

您唯一真正的选择是确保您的请求使用 CORS 安全的方法标头。这可能意味着省略您将包含的自定义标题(如X-Requested-With),更改Content-Type,或更多。

无论你做什么,你都必须确保你有适当的 CSRF 保护,因为 CORS 不会阻止所有不安全的请求。正如最初的规范所说:“简单请求对检索具有重要意义的资源必须保护自己免受跨站点请求伪造”。

于 2017-01-17T01:36:41.470 回答
66

考虑 CORS 之前的跨域请求世界。您可以执行标准形式的 POST,或使用 ascriptimage标签来发出 GET 请求。除了 GET/POST 之外,您不能发出任何其他请求类型,也不能针对这些请求发出任何自定义标头。

随着 CORS 的出现,规范作者面临着在不破坏 Web 现有语义的情况下引入新的跨域机制的挑战。他们选择通过为服务器提供一种选择加入任何新请求类型的方法来做到这一点。此选择加入是预检请求。

因此,没有任何自定义标头的 GET/POST 请求不需要预检,因为这些请求在 CORS 之前就已经可以实现。但是任何带有自定义标头的请求或 PUT/DELETE 请求需要预检,因为这些是 CORS 规范的新内容。如果服务器对 CORS 一无所知,它将在没有任何 CORS 特定标头的情况下进行回复,并且不会发出实际请求。

如果没有预检请求,服务器可能会开始看到来自浏览器的意外请求。如果服务器没有为这些类型的请求做好准备,这可能会导致安全问题。CORS 预检允许以安全的方式将跨域请求引入 Web。

于 2013-03-13T12:49:24.950 回答
42

CORS 允许您指定比以前使用跨域<img src><form action>.

假设浏览器无法发出,例如跨域DELETE请求或带有X-Requested-With标头的跨域请求,某些服务器可能(很差)受到保护,因此此类请求是“受信任的”。

为了确保服务器真正支持 CORS 而不仅仅是响应随机请求,执行预检。

于 2013-03-13T11:43:08.703 回答
17

这是另一种看待它的方式,使用代码:

<!-- hypothetical exploit on evil.com -->
<!-- Targeting banking-website.example.com, which authenticates with a cookie -->
<script>
jQuery.ajax({
  method: "POST",
  url: "https://banking-website.example.com",
  data: JSON.stringify({
    sendMoneyTo: "Dr Evil",
    amount: 1000000
  }),
  contentType: "application/json",
  dataType: "json"
});
</script>

在 CORS 之前,上面的利用尝试会失败,因为它违反了同源策略。以这种方式设计的 API 不需要 XSRF 保护,因为它受到浏览器的本机安全模型的保护。预 CORS 浏览器不可能生成跨域 JSON POST。

现在 CORS 出现了——如果不需要通过飞行前选择加入 CORS,那么这个站点就会突然出现一个巨大的漏洞,这不是他们自己的过错。

为了解释为什么允许某些请求跳过预飞行,规范对此进行了回答:

一个简单的跨域请求已被定义为与当前部署的不符合本规范的用户代理可能生成的请求一致。

为了解决这个问题,GET 没有预先飞行,因为它是 7.1.5 定义的“简单方法”。(标题也必须是“简单的”以避免预飞行)。这样做的理由是“简单”的跨域 GET 请求可能已经由 eg 执行<script src="">(这就是 JSONP 的工作方式)。由于任何具有src属性的元素都可以触发跨域 GET,而无需预检,因此要求对“简单”XHR 进行预检没有安全优势。

于 2016-05-05T01:01:58.513 回答
16

我觉得其他答案并没有关注战前增强安全性的原因。

场景:

1)飞行前攻击者在用户通过safe-bank.com身份验证时伪造来自站点 dummy-forums.com
的请求方法。服务器不知道浏览器期望作为响应的 CORS,因此浏览器将不会继续(没有任何危害) 2)没有 pre-flight。攻击者在与上述相同的场景下伪造请求,浏览器将立即发出 POST 或 PUT 请求,服务器接受并可能处理它,这可能会造成一些危害。



如果攻击者直接从某个随机主机跨源发送请求,则很可能有人正在考虑没有身份验证的请求。这是一个伪造的请求,但不是 xsrf 请求。因此服务器将检查凭据并失败。CORS 不会尝试阻止具有发出请求的凭据的攻击者,尽管白名单可以帮助减少这种攻击向量。

飞行前机制增加了客户端和服务器之间的安全性和一致性。我不知道这是否值得为每个请求进行额外的握手,因为缓存在那里很难使用,但这就是它的工作原理。

于 2015-09-14T15:54:47.093 回答
3

此外,对于可能对用户数据造成副作用的HTTP 请求方法(特别是对于 GET 以外的 HTTP 方法,或者对于某些 MIME 类型的 POST 使用),规范要求浏览器“预检”请求

来源

于 2013-03-13T12:28:12.760 回答
2

在支持 CORS 的浏览器中,读取请求(如 GET)已经受到同源策略的保护:试图发出经过身份验证的跨域请求(例如向受害者的网上银行网站或路由器的配置界面)的恶意网站不会能够读取返回的数据,因为银行或路由器没有设置Access-Control-Allow-Origin标头。

但是,通过写入请求(如 POST),当请求到达网络服务器时,损坏就完成了。*网络服务器可以检查Origin标头以确定请求是否合法,但这种检查通常不会实现,因为网络服务器不需要对于 CORS 或网络服务器比 CORS 更旧,因此假设同源策略完全禁止跨域 POST。

这就是为什么网络服务器有机会选择接收跨域写入请求的原因。

* 本质上是 AJAX 版本的 CSRF。

于 2017-06-23T03:55:31.583 回答
1

不是关于Performance的预检请求吗?通过预检请求,客户端可以在发送大量数据之前快速知道该操作是否被允许,例如在 JSON 中使用 PUT 方法。或者在通过网络传输身份验证标头中的敏感数据之前。

除了自定义标头之外,PUT、DELETE 和其他方法的事实是默认不允许的(它们需要“Access-Control-Request-Methods”和“Access-Control-Request-Headers”的明确许可),听起来就像双重检查一样,因为这些操作可能对用户数据产生更多影响,而不是 GET 请求。所以,听起来像:

“我看到您允许来自http://foo.example的跨站点请求,但是您确定您将允许 DELETE 请求吗?您是否考虑过这些请求可能对用户数据造成的影响?”

我不理解预检请求与旧服务器优势之间的引用相关性。在 CORS 之前实现或没有 CORS 意识的 Web 服务将永远不会收到任何跨站点请求,因为首先它们的响应不会有“Access-Control-Allow-Origin”标头。

于 2015-05-02T20:02:06.233 回答