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

问题描述 投票:304回答:9

跨源资源共享是一种允许网页将XMLHttpRequests发送到另一个域(来自wikipedia)的机制。

在过去的几天里,我一直在调整CORS,我想我对一切运作方式都有很好的理解。

所以我的问题不是关于CORS /预检如何工作,而是关于将预检作为新请求类型的原因。我没有看到任何理由为什么服务器A需要向服务器B发送预检(PR)以确定是否接受真实请求(RR) - B当然可以接受/拒绝RR而不用任何先前的公关。

经过相当多的搜索后,我在www.w3.org(7.1.5)找到了this piece的信息:

为了保护资源免受在此规范存在之前不能源自某些用户代理的跨源请求,进行预检请求以确保资源知道此规范。

我发现这是最难理解的句子。我的解释(应该更好地称之为'最佳猜测')是关于保护服务器B免受来自服务器C的不知道规范的请求。

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

ajax html5 http cors preflight
9个回答
292
投票

我花了一些时间对飞行前请求的目的感到困惑,但我想我现在已经得到了它。

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

预检请求与安全性无关,它们与现在正在开发的应用程序无关,并且具有CORS的意识。相反,预检机制有利于在没有CORS意识的情况下开发的服务器,它可以作为客户端和服务器之间的完整性检查,它们都是CORS感知的。 CORS的开发人员觉得有足够的服务器依赖于他们永远不会收到的假设,例如跨域DELETE请求,他们发明了预检机制,允许双方选择加入。他们认为,只需启用跨域调用的替代方案就会破坏太多现有应用程序。

这里有三种情况:

  1. 旧服务器,不再开发,并在CORS之前开发。这些服务器可以做出他们永远不会收到的假设,例如跨域DELETE请求。这种情况是预检机制的主要受益者。是的,这些服务可能已经被恶意或不符合要求的用户代理滥用(并且CORS没有做任何改变),但在具有CORS的世界中,预检机制提供额外的“健全性检查”,以便客户端和服务器不会因为网络的基本规则已经改变而中断。
  2. 仍在开发中但包含大量旧代码的服务器,并且审计所有旧代码以确保它在跨域社区中正常工作是不可行/不可取的。这种情况允许服务器逐步选择加入CORS,例如通过说“现在我将允许这个特定的标题”,“现在我将允许这个特定的HTTP动词”,“现在我将允许发送cookie / auth信息”,等等。这种情况受益于预检机制。
  3. 编写了具有CORS意识的新服务器。根据标准安全实践,服务器必须在面对任何传入请求时保护其资源 - 服务器不能信任客户端不做恶意事情。这种情况不会受益于预检机制:预检机制不会为正确保护其资源的服务器带来额外的安全性。

153
投票

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

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

这就是this part of the specification的意思:“为了保护资源免受在此规范存在之前不能源自某些用户代理的跨源请求的影响,产生预检请求以确保资源知道此规范。”

能给我举个例子?

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

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

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

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

为什么所有这些关于浏览器的大惊小怪,攻击者不能只是从他们自己的计算机发送DELETE请求?

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

这听起来像Cross-Site Request Forgery,其中一个表格B.com可以用用户的饼干POSTA.com并做损坏。

那就对了。另一种方法是创建预检请求,以便不增加非CORS感知服务器的CSRF攻击面。

但是看看requirements的“简单”请求不需要预检,我看到仍然允许POST。这可以像DELETE一样改变状态和删除数据!

确实如此! CORS不保护您的站点免受CSRF攻击。然后,如果没有CORS,您也无法免受CSRF攻击。预检请求的目的只是为了限制您的CSRF暴露于CORS前世界已经存在的东西。

叹。好的,我勉强接受了对飞行前请求的需求。但为什么我们必须为服务器上的每个资源(URL)执行此操作?服务器要么处理CORS,要么不处理。

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

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

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

这是一个谨慎的决定,优先考虑安全性而不是性能,允许浏览器立即实施CORS规范而不会使现有服务器面临风险吗?或者,为了在特定时间容纳特定服务器中的错误而使互联网浪费带宽和延迟加倍,这是否是短视的?

意见不同。

那么,至少浏览器会为单个URL缓存预检?

是。虽然可能不会持续很长时间。在WebKit浏览器中,最大预检缓存时间是currently 10 minutes

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

您唯一真正的选择是确保您满足requirements的“简单”请求。这可能意味着遗漏您可能包含的自定义标题(如X-Requested-With),关于Content-Type或更多。

无论你做什么,你必须确保你有适当的CSRF保护,因为CORS规范没有解决拒绝“简单”请求,包括不安全的POSTAs the specification puts it:“简单请求具有除检索之外的重要性的资源必须保护自己免受跨站点请求伪造”。


50
投票

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

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

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

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


31
投票

CORS允许您指定比以前使用跨源<img src><form action>更多的标题和方法类型。

一些服务器可能已被(差)保护,假设浏览器无法做到,例如,使用DELETE标头的跨源X-Requested-With请求或跨源请求,因此这些请求是“可信的”。

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


15
投票

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

<!-- 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,突然这个网站会有一个巨大的漏洞,而不是他们自己的过错。

解释为什么允许一些请求跳过飞行前的this is answered by the spec:

已经将简单的跨源请求定义为与可能由当前部署的不符合该规范的用户代理生成的请求一致。

为了解释这一点,GET不是预先通过的,因为它是7.1.5定义的“简单方法”。 (标题也必须“简单”以避免飞行前)。对此的理由是“简单的”跨源GET请求可能已经由例如<script src="">(这是JSONP的工作方式)。由于具有src属性的任何元素都可以触发跨源GET,没有预飞行,因此要求对“简单”XHR进行预打击没有任何安全优势。


12
投票

我觉得其他答案并没有集中在预先战斗增强安全性的原因上。

场景:

1)在飞行前。当用户通过safe-bank.com验证时,攻击者伪造来自网站dummy-forums.com的请求 如果服务器没有检查原点,并且以某种方式存在缺陷,浏览器将发出飞行前请求,OPTION方法。服务器不知道浏览器期望作为响应的CORS,因此浏览器不会继续(不会造成任何伤害) 2)没有飞行前。攻击者在上述相同的情况下伪造请求,浏览器将立即发出POST或PUT请求,服务器接受它并可能处理它,这可能会造成一些伤害。

如果攻击者直接发送请求,来自某个随机主机,则最有可能是在考虑没有身份验证的请求。这是伪造的请求,但不是xsrf请求。所以服务器将检查凭据并失败。虽然白名单可以帮助减少这种攻击向量,但CORS并不会阻止具有凭据的攻击者发出请求。

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


3
投票

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

Source


1
投票

是不是有关性能的预先要求?利用预检请求,客户端可以在发送大量数据之前快速知道是否允许操作,例如,使用PUT方法在JSON中。或者通过线路在认证标题中传输敏感数据之前。

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

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

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


1
投票

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

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

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

*基本上是CSRF的AJAX版本。

© www.soinside.com 2019 - 2024. All rights reserved.