跨源资源共享是一种机制,允许网页向另一个域发送xmlhttprequest(来自维基百科)。

在过去的几天里,我一直在摆弄CORS,我认为我对每件事情的工作原理都有很好的理解。

所以我的问题不是关于CORS /预飞是如何工作的,而是关于将预飞作为一种新的请求类型背后的原因。我看不出为什么服务器A需要发送一个preflight (PR)到服务器B,只是为了找出真实的请求(RR)是否会被接受——B当然有可能在没有任何预先PR的情况下接受/拒绝RR。

在搜索了相当多之后,我在www.w3.org(7.1.5)上找到了这条信息:

为了保护资源不受在本规范存在之前不能来自某些用户代理的跨源请求的影响,a 发出飞行前请求,以确保资源知道这一点 规范。

我发现这是最难理解的句子。我的解释(最好称之为“最佳猜测”)是关于保护服务器B不受服务器C的请求,因为服务器C不知道该规范。

谁能解释一个场景/展示一个PR + RR比RR单独解决更好的问题?


当前回答

CORS允许您指定比以前cross-origin <img src>或<form action>更多的头文件和方法类型。

一些服务器可能已经(很差地)受到了浏览器无法做出的假设的保护,例如跨源DELETE请求或带有X-Requested-With头的跨源请求,因此这样的请求是“可信的”。

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

其他回答

CORS允许您指定比以前cross-origin <img src>或<form action>更多的头文件和方法类型。

一些服务器可能已经(很差地)受到了浏览器无法做出的假设的保护,例如跨源DELETE请求或带有X-Requested-With头的跨源请求,因此这样的请求是“可信的”。

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

此外,对于HTTP请求方法可能导致的副作用 用户数据(特别是对于GET以外的HTTP方法或POST 使用特定的MIME类型),规范要求 浏览器“预先处理”请求

考虑一下CORS之前的跨域请求世界。您可以执行标准表单POST,或者使用脚本或图像标记来发出GET请求。除了GET/POST之外,您不能创建任何其他请求类型,并且不能对这些请求发出任何自定义头。

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

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

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

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

<!-- 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请求已经可以通过例如<script src="">来执行(这是JSONP的工作方式)。由于任何具有src属性的元素都可以触发跨源GET,没有预先飞行,因此在“简单”xhr上要求预先战斗将没有安全好处。

引入飞行前请求的动机是什么?

引入了Preflight请求,以便浏览器在发送特定请求之前可以确定它正在处理一个支持cors的服务器。这些请求被定义为既具有潜在危险(状态改变)又具有新请求(由于同源政策,在CORS之前是不可能的)。使用飞行前请求意味着服务器必须选择(通过正确地响应飞行前请求)接受CORS使之成为可能的新的、具有潜在危险的请求类型。

这就是原始规范的这一部分的含义:“为了保护资源不受在此规范存在之前不能来自某些用户代理的跨起源请求的影响,发出预飞行请求以确保资源知道此规范。”

你能给我举个例子吗?

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

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

浏览器可以简单地发送DELETE并让服务器决定如何处理它。但是如果。com不知道CORS协议怎么办?它可能会继续执行危险的DELETE。由于浏览器的同源策略(Same Origin policy),它可能认为它永远不会收到这样的请求,因此它可能从来没有对这样的攻击进行过加固。

为了保护这些不支持cors的服务器,该协议要求浏览器首先发送一个飞行前请求。这种新的请求只有支持cors的服务器才能正确响应,从而允许浏览器知道发送实际的DELETE是否安全。

为什么浏览器这么麻烦,攻击者就不能从他们自己的电脑发送一个DELETE请求吗?

当然,但是这样的请求不包括用户的cookie。这种设计用来防止的攻击依赖于这样一个事实:浏览器将在请求的同时为其他域发送cookie(特别是用户的身份验证信息)。

这听起来像是跨站请求伪造,即站点B.com上的表单可以用用户的cookie提交给A.com,并造成破坏。

这是正确的。另一种说法是,创建预飞行请求是为了不增加非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缓存preflight ?

是的。虽然可能不会持续太久。在WebKit浏览器中,最大的预缓存时间目前是10分钟。

叹息。好吧,如果我知道我的服务器是cors感知的,因此不需要飞行前请求提供的保护,有什么方法可以避免它们吗?

您唯一的选择是确保您的请求使用cors安全的方法和头。这可能意味着省略您本来应该包含的自定义头文件(如X-Requested-With),更改Content-Type等等。

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