CORS-引入飞行前要求的动机是什么?


366

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

在过去的几天里,我一直在摆弄CORS,我认为我对所有工作原理都非常了解。

因此,我的问题不是关于CORS /预检如何工作,而是关于将预检作为新的请求类型的原因。我看不到任何原因,为什么服务器A需要发送预检(PR)到服务器B只是为了确定是否接受实际请求(RR)-B肯定有可能在没有请求的情况下接受/拒绝RR任何以前的公关。

经过大量搜索后,我在www.w3.org(7.1.5)上找到了以下信息:

为了保护资源免遭在该规范存在之前可能源自某些用户代理的跨域请求,将进行预检请求以确保资源了解此规范。

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

有人可以解释一种情况/显示PR + RR比单独使用RR更能解决问题吗?

Answers:


323

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

关键见解是,起飞前的要求不是安全的事情。相反,它们是不变的规则

预检请求与安全无关,并且与具有CORS意识的当前正在开发的应用程序无关。而是,预检机制会使在不了解CORS的情况下开发的服务器受益,并且它充当客户端和服务器之间是否都是CORS意识的健全性检查。CORS的开发人员认为那里有足够的服务器依赖于它们永远不会收到的假设,例如,跨域DELETE请求发明了预检机制以允许双方选择加入。他们认为,仅允许跨域调用的替代方案将破坏太多现有应用程序。

这里有三种情况:

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

  2. 仍在开发中的服务器,但其中包含许多旧代码,并且对于这些服务器而言,不可行/不希望审核所有旧代码以确保其在跨域环境中正常运行。这种情况允许服务器逐步加入CORS,例如说“现在我将允许这个特定的标头”,“现在我将允许这个特定的HTTP动词”,“现在我将允许cookie / auth信息成为已发送”等。这种情况将从预检机制中受益。

  3. 编写具有CORS意识的新服务器。根据标准的安全惯例,服务器必须面对任何传入请求来保护其资源-服务器无法信任客户端不进行恶意操作。此方案无法从预检机制中受益:预检机制不会为已正确保护其资源的服务器带来额外的安全性。


12
如果是这种情况,为什么要在每个请求中发送?每个服务器一个请求应该足以确定服务器是否知道CORS。
道格拉斯·弗格森

3
该规范在浏览器中包含预检结果缓存。因此,尽管仍然感到混乱和安全性低下,但似乎可以将新服务器配置为无限期缓存预检。
Michael Cole 2015年

7
我同意预检请求与安全性没有内在联系,但这听起来像是CORS出于安全原因使用预检请求。这不仅仅是为了防止相对无害的错误情况而进行的健全性检查。如果用户代理盲目地将请求发送到服务器,则错误地假定服务器实施了CORS,则很可能会接受跨站点请求伪造。即使该响应不能被javascript读取,服务器也可能已经采取了一些不受欢迎的措施,例如删除帐户或进行银行转帐。
亚历山大·泰勒

5
问题是,preflight-result-cache基本上是无用的,因为1.它仅适用于确切的请求,而不适用于整个域,因此无论如何所有请求都将在第一次进行预检;2.根据实施,在大多数浏览器中,该时间限制为10分钟,因此甚至不是无限期地接近。
davidgoli '16

2
@VikasBansal现有服务器必须“选择加入”并同意通过配置它们对预检选项请求的答复方式跨源共享它们的资源。如果他们没有明确回答预检请求,浏览器将不会发出实际请求。毕竟,并非所有服务器都希望接受跨域请求。
凯文·李

215

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

引入了预检请求,以便浏览器在发送某些请求之前可以确定它正在处理支持CORS的服务器。这些请求被定义为既具有潜在危险(状态更改)又是新的请求(由于“ 同源起源策略”,在CORS之前是不可能的)。使用预检请求意味着服务器必须(通过正确响应预检)选择接受CORS可能导致的新的,潜在危险的请求类型。

这就是规范这一部分的含义:“为了保护资源免受在该规范存在之前可能源自某些用户代理的跨域请求,将进行预检请求以确保资源了解此规范。”

能给我举个例子?

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

在CORS之前,浏览器的“相同来源策略”将阻止其发送此请求。但是,由于CORS的目的是使这种跨域通信成为可能,所以这不再合适。

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

然后,为了保护此类不支持CORS的服务器,该协议要求浏览器首先发送预检请求。这种新的请求是只有支持CORS的服务器才能正确响应的请求,使浏览器可以知道发送实际请求是否安全DELETE

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

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

这听起来像跨站请求伪造,其中在网站上的表单B.com可以POSTA.com用户的cookies和进行破坏。

那就对了。进行这种处理的另一种方式是创建预检请求,以免增加非可识别CORS的服务器的CSRF攻击面。

但看的要求,对于不要求preflights“简单”的要求,我看POST还是允许的。这样就可以更改状态并删除数据DELETE

确实如此!CORS不能保护您的站点免受CSRF攻击。再有,如果没有CORS,您也不会免受CSRF攻击。飞行前请求的目的仅仅是将CSRF的暴露范围限制在CORS之前的世界中。

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

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

精细。让我们妥协吧。让我们创建一个新的CORS标头,该标头允许服务器确切说明它可以使用的资源,从而可以避免对这些URL进行其他预检请求。

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

这是一个明智的决定,即优先考虑安全性而不是性能,以允许浏览器立即实施CORS规范而不会给现有服务器带来风险吗?还是为了将Internet浪费在带宽上而使延迟加倍,只是为了适应特定时间在特定服务器中的错误,是否是短视?

意见不同。

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

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

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

你唯一真正的选择是,以确保您符合要求的“简单”的请求。这可能意味着遗漏了您可能会包含的自定义标头(如X-Requested-With),位于Content-Type或附近。

无论做什么,都必须确保已设置适当的CSRF保护,因为CORS规范未解决拒绝“简单”请求(包括不安全请求)的问题POST正如规范所指出的那样:“对于简单请求具有重要意义而不是检索的资源,必须保护自己免受跨站点请求伪造的侵害”。


20
这是我在CORS上阅读的最好的入门文章。谢谢!
KIV

4
很棒的解释。
普拉茨

4
这是我在该主题上看到的最佳答案。很好解释!
alaboudi

3
CORS是一个棘手的材料,这个帖子揭示了一些隐藏的要点
Stanislav Verjikovskiy

1
@Yos:浏览器将包含这些cookie,因为这是期望浏览器正常工作的方式(在RFC 6265之类的标准中已得到编码)。浏览器是否对选项卡使用单独的进程是一个实现细节,它不会阻止它发送cookie。
凯文·克里斯托弗·亨利

51

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

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

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

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


如何通过脚本/ img标签发出POST请求?
奇特

2
你不能 我的意思是您可以执行POST形式,使用script / img执行GET。我编辑了帖子,希望可以澄清这一点。
monsur 2013年

我知道了。这就说得通了。
奇特

5
感谢您的回答,这肯定完成了我的照片!不幸的是,我仍然看不到预检背后的中心点。关于您的答案:什么是意外请求 ”是什么?在非飞行前世界中,比在飞行前世界中发生的“更多”意外/安全性如何(例如丢失飞行前或仅简单地“忘记”飞行前浏览器的恶意浏览器)?
jan groth's 2013年

7
可能有一些API依赖于浏览器的同源策略来保护其资源。它们应该具有附加的安全性,但是它们依赖于同源策略。没有预检,其他域上的用户现在可以向API发出请求。API将假定该请求有效(因为它对CORS一无所知)并执行该请求。浏览器可能阻止响应到达用户,但此时,损害可能已经造成。如果请求是PUT / DELETE,则资源可能已被更新或删除。
蒙苏尔

37

CORS可以让你比跨域以前可能指定更多的头部和方法类型<img src><form action>

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

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


12
这应该是公认的答案。这是最明确,最关键的。本质上,飞行前请求的唯一目的是将CORS之前的Web标准与CORS以后的Web标准集成在一起。
斩波器lion4 '16

2
我喜欢这个答案,但我觉得这并不是全部原因……“信任假设”必须仅适用于仅浏览器可以做的事情(特别是,将浏览器的用户信息发送到他们的域内-即Cookie)。如果这不是假设的一部分,那么跨域浏览器请求可以做的任何事情都可以由第三方非浏览器代理完成,对吗?
Fabio Beltramini

2
@FabioBeltramini对,非浏览器可以发送他们想要的任何东西。但是,通过浏览器进行的攻击是特殊的,因为您可以使其他人的浏览器通过自己的IP,自己的Cookie等来执行操作
。– Kornel

我开始看到真正的问题。感谢您的评论和回复@FabioBeltramini和Kronel的回复。如果飞行前检查不存在,则攻击者可以在其站点上放置一些JavaScript代码,但可以从其他许多人的计算机上执行这些代码。所有其他客户端(包括移动应用程序)都很难“雇用”其他人来做到这一点。
小鹏-ZenUML.com

16

这是使用代码的另一种查看方式:

<!-- 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进行预检查不会对安全产生任何好处。


1
@MilesRout:Telnet不是预检旨在缓解的威胁模型的一部分。预检与浏览器有关:1)依靠存储的“环境权限”(例如cookie)和2)可以欺骗第三方滥用该权限(例如,跨站点请求伪造)。广义模型被称为混淆副问题
Dylan Tack

但是,这就是环境权限的问题,您随时可以滥用它。
Miles Rout

13

我觉得其他的答案不是集中在事前提高安全性的原因上。

场景:

1)飞行前。在用户通过safe-bank.com身份验证时,攻击者伪造了来自dummy-forums.com网站的请求。
如果服务器未检查来源,并且以某种方式存在缺陷,浏览器将发出飞行前请求OPTION方法。服务器不知道浏览器期望将其作为响应的CORS,因此浏览器将不会继续运行(不会造成任何危害)

2)没有预检。攻击者在与上述相同的情况下伪造该请求,浏览器将立即发出POST或PUT请求,服务器会接受并可能处理该请求,这可能会造成一些危害。

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

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


同意针对@ michael-iles答复中提到的“新服务器”仍然可能出现的CSRF攻击问题。
eel ghEEz 2015年

这是一个有用的描述,可能值得在其他地方进行记录。也许考虑将其添加到MDN页面之一?
sideshowbarker

但是,为什么诸如带有Content-Type文本/纯文本的POST之类的请求不执行飞行前请求?在我看来,如果安全性成为问题,则每个“写入”请求(POST,PUT,DELETE)都应具有此飞行前请求。
以色列丰塞卡

带有文本/纯文本的POST被认为是一个简单的请求-请注意,如果源不匹配,浏览器将不会显示响应(如果未将服务器配置为CORS,则为这种情况)。
Hirako

在攻击方面,有一些有趣的事情可以做,利用大多数浏览器可以接受并发送简单请求这一事实。例如这个
平子


2

飞行前请求对于可以更改服务器状态的请求是必需的。有2种类型的请求-

1)无法更改服务器状态的呼叫(例如GET) -用户可能会收到请求的响应(如果服务器未检查来源),但是如果请求域未添加到响应头中,则Access-Control-允许来源,浏览器不会向用户显示数据,即,请求是从浏览器发送的,但用户无法查看/使用响应。

2)可以更改服务器状态的调用(如POST,DELETE) -从1开始,我们发现浏览器不会阻止请求,但会阻止响应,因此,在没有事先检查的情况下,不允许进行状态更改调用。即使对浏览器的响应可能失败,此类调用也可能会更改不检查调用源的信任服务器(称为跨站点请求伪造)。因此,我们具有预检请求的概念,该请求在将任何状态更改的调用发送到服务器之前先进行OPTIONS调用。


1

不是关于性能的事前要求吗?使用预检的请求,客户端可以在发送大量数据之前快速了解是否允许该操作,例如,使用JSON和PUT方法。或在通过身份验证标头传输敏感数据之前。

默认情况下,除自定义标头外,不允许PUT,DELETE和其他方法的事实(它们需要“ Access-Control-Request-Methods”和“ Access-Control-Request-Headers”的显式权限),听起来就像仔细检查一样,因为这些操作可能会对用户数据(而不是GET请求)产生更多影响。因此,听起来像:

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

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


4
您误会了Access-Control-Allow-Origin。缺少该标头不会阻止浏览器发送请求,而只会阻止JS读取响应中的数据。
Dylan Tack 2015年

您能否解释一下“缺少该标头并不会阻止浏览器发送请求,而只是阻止JS能够读取响应中的数据”,我无法完全理解。
SiddharthBhagwan

@DylanTack好点。这让我感到疑惑,为什么GET xhr也不能进行预检?尽管不太可能,但是GET请求也可能是有害的/数据变异的。另外,由于所有这些都可以使用CSRF来解决,因此在我看来,这似乎是浏览器对服务器的过度保护,而这些服务器过于疏忽,无法实施常见的安全措施。
佩勒格

公认的答案很好地说明了这一点,它是“不变的事情”(与CORS存在之前创建的网站具有向后兼容性)。看到代码仍然很有趣,因此我发布了另一个带有代码示例的答案。
迪伦·塔克

1

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

但是,通过写入请求(如POST),当请求到达Web服务器时会造成损害。* Web服务器可以检查Origin标头以确定请求是否合法,但是由于其中一个Web服务器都不需要,因此通常不执行此检查适用于CORS或Web服务器的版本早于CORS,因此假设同源规则完全禁止跨域POST。

这就是为什么让网络服务器有机会选择接收跨域写请求

*本质上是CSRF的AJAX版本。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.