跨域资源共享是一种允许网页向另一个域(来自 Wikipedia)发出 XMLHttpRequest 的机制。
在过去的几天里,我一直在摆弄 CORS,我想我对一切是如何运作的有了很好的理解。
所以我的问题不是关于 CORS / preflight 如何工作,而是关于将 preflights 作为新请求类型提出的原因。我看不出为什么服务器 A 需要向服务器 B 发送预检 (PR) 只是为了确定是否会接受真正的请求 (RR) - B 肯定有可能接受/拒绝 RR任何先前的 PR。
经过大量搜索后,我在 www.w3.org (7.1.5) 找到了 this piece 的信息:
为了保护资源免受在本规范存在之前无法源自某些用户代理的跨域请求,发出预检请求以确保资源了解本规范。
我发现这是有史以来最难理解的句子。我的解释(最好称其为“最佳猜测”)是关于保护服务器 B 免受来自不了解规范的服务器 C 的请求。
有人可以解释一个场景/展示一个 PR + RR 比单独使用 RR 解决的问题吗?
我花了一些时间对预检请求的目的感到困惑,但我想我现在明白了。
关键的见解是预检请求不是安全问题。相反,它们是不改变规则的东西。
预检请求与安全无关,它们与现在正在开发的应用程序无关,具有 CORS 意识。相反,预检机制有益于在没有意识到 CORS 的情况下开发的服务器,并且它充当客户端和服务器之间的健全性检查,它们都支持 CORS。 CORS 的开发人员认为那里有足够多的服务器依赖于他们永远不会收到的假设,例如,他们发明了预检机制以允许双方选择加入的跨域删除请求。他们认为,如果只是简单地启用跨域调用,就会破坏太多现有的应用程序。
这里有三种情况:
旧服务器,不再在开发中,在 CORS 之前开发。这些服务器可能会假设它们永远不会收到例如跨域删除请求。这种情况是预检机制的主要受益者。是的,这些服务可能已经被恶意或不合格的用户代理滥用(而 CORS 没有改变这一点),但在使用 CORS 的世界中,预检机制提供了额外的“健全性检查”,因此客户端和服务器不会打破,因为网络的基本规则已经改变。仍在开发中但包含大量旧代码的服务器,并且不可行/不希望审核所有旧代码以确保其在跨域世界中正常工作。这种情况允许服务器逐步选择加入 CORS,例如通过说“现在我将允许这个特定的标头”、“现在我将允许这个特定的 HTTP 动词”、“现在我将允许 cookie/auth 信息发送”等。这种情况得益于预检机制。以 CORS 意识编写的新服务器。根据标准安全实践,服务器必须在面对任何传入请求时保护其资源——服务器不能相信客户端不会做恶意事情。这种情况不会从预检机制中受益:预检机制不会为已正确保护其资源的服务器带来额外的安全性。
引入预检请求的动机是什么?
引入了预检请求,以便浏览器在发送某些请求之前可以确定它正在处理可识别 CORS 的服务器。这些请求被定义为具有潜在危险(状态改变)和新的(由于 Same Origin Policy 而在 CORS 之前不可能)的请求。使用预检请求意味着服务器必须选择加入(通过正确响应预检)CORS 实现的新的、具有潜在危险的请求类型。
这就是原始规范 this part 的含义:“为了保护资源免受在本规范存在之前无法源自某些用户代理的跨域请求,发出预检请求以确保资源了解本规范。”
能给我举个例子?
让我们假设浏览器用户在 A.com
登录到他们的银行网站。当他们导航到恶意 B.com
时,该页面包含一些尝试向 A.com/account
发送 DELETE
请求的 Javascript。由于用户已登录到 A.com
,因此该请求(如果发送)将包括识别用户的 cookie。
在 CORS 之前,浏览器的同源策略会阻止它发送此请求。但既然 CORS 的目的就是让这种跨域通信成为可能,那已经不合适了。
浏览器可以简单地发送 DELETE
并让服务器决定如何处理它。但是如果 A.com
不知道 CORS 协议怎么办?它可能会继续执行危险的 DELETE
。它可能会假设——由于浏览器的同源策略——它永远不会收到这样的请求,因此它可能永远不会针对这种攻击进行强化。
为了保护这种不支持 CORS 的服务器,协议要求浏览器首先发送 预检请求。这种新类型的请求只有 CORS 感知服务器才能正确响应,从而允许浏览器知道发送实际 DELETE
是否安全。
为什么要对浏览器大惊小怪,攻击者不能只从自己的计算机发送一个 DELETE
请求吗?
当然可以,但是这样的请求不会包含用户的 cookie。旨在防止的攻击依赖于浏览器将与请求一起发送其他域的 cookie(特别是用户的身份验证信息)这一事实。
这听起来像 Cross-Site Request Forgery,其中网站 B.com
上的表单可以与用户的 cookie 一起提交给 A.com
并造成损害。
这是正确的。另一种说法是,创建预检请求是为了不增加非 CORS 感知服务器的 CSRF 攻击面。
但是 POST
是 listed 作为不需要预检的方法。这可以像 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,因此不需要预检请求提供的保护,我有什么办法可以避免它们吗?
您唯一真正的选择是确保您的请求使用 CORS 安全的 methods 和 headers。这可能意味着省略您本来应该包含的自定义标头(例如 X-Requested-With
)、更改 Content-Type
或更多。
无论你做什么,你都必须确保你有适当的 CSRF 保护,因为 CORS 不会阻止所有不安全的请求。作为原始规范puts it:“简单请求具有非检索意义的资源必须保护自己免受跨站点请求伪造”。
B.com
的页面由 Javascript 触发的对 A.com
的请求。
DELETE
,服务器可能会执行它。这就是我们试图通过预检来防止的问题。对于安全请求,我们不必保护服务器,因此在这种情况下不需要预检,浏览器可以仅依靠标头来决定要做什么。
DELETE
。您链接到的示例使用 POST
和 GET
,通常不会被阻止,我稍后会在答案中讨论。
考虑 CORS 之前的跨域请求世界。您可以执行标准表单 POST,或使用 script
或 image
标记来发出 GET 请求。除了 GET/POST 之外,您不能发出任何其他请求类型,也不能针对这些请求发出任何自定义标头。
随着 CORS 的出现,规范作者面临着在不破坏 Web 现有语义的情况下引入新的跨域机制的挑战。他们选择通过为服务器提供一种选择加入任何新请求类型的方法来做到这一点。此选择加入是预检请求。
因此,没有任何自定义标头的 GET/POST 请求不需要预检,因为这些请求在 CORS 之前就已经可以实现。但是任何带有自定义标头的请求或 PUT/DELETE 请求都需要预检,因为这些是 CORS 规范的新内容。如果服务器对 CORS 一无所知,它将在没有任何 CORS 特定标头的情况下进行回复,并且不会发出实际请求。
如果没有预检请求,服务器可能会开始看到来自浏览器的意外请求。如果服务器没有为这些类型的请求做好准备,这可能会导致安全问题。 CORS 预检允许以安全的方式将跨域请求引入 Web。
与以前使用跨域 <img src>
或 <form action>
相比,CORS 允许您指定更多的标头和方法类型。
假设浏览器无法发出,例如跨域 DELETE
请求或带有 X-Requested-With
标头的跨域请求,某些服务器可能已(较差)受到保护,因此此类请求是“可信的”。
为了确保服务器真正支持 CORS 而不仅仅是碰巧响应随机请求,执行了预检。
这是另一种看待它的方式,使用代码:
<!-- 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 进行预检没有安全优势。
我觉得其他答案并没有关注战前增强安全性的原因。
场景:
1) 飞行前。攻击者在用户通过 safe-bank.com 身份验证时伪造来自站点 dummy-forums.com 的请求方法。服务器不知道浏览器期望作为响应的 CORS,因此浏览器将不会继续(没有任何危害) 2)没有预飞行。攻击者在与上述相同的场景下伪造请求,浏览器将立即发出 POST 或 PUT 请求,服务器接受并可能处理它,这可能会造成一些危害。
如果攻击者直接从某个随机主机跨源发送请求,则很可能有人正在考虑没有身份验证的请求。这是一个伪造的请求,但不是 xsrf 请求。因此服务器将检查凭据并失败。 CORS 不会尝试阻止具有发出请求的凭据的攻击者,尽管白名单可以帮助减少这种攻击向量。
飞行前机制增加了客户端和服务器之间的安全性和一致性。我不知道这是否值得为每个请求进行额外的握手,因为缓存在那里很难使用,但这就是它的工作原理。
在支持 CORS 的浏览器中,读取请求(如 GET)已经受到同源策略的保护:恶意网站试图发出经过身份验证的跨域请求(例如向受害者的网上银行网站)或路由器的配置接口)将无法读取返回的数据,因为银行或路由器没有设置 Access-Control-Allow-Origin
标头。
但是,编写请求(如 POST)会在请求到达网络服务器时造成损坏。*网络服务器可以检查 Origin
标头以确定请求是否合法,但此检查是通常没有实现,因为网络服务器不需要 CORS 或者网络服务器比 CORS 旧,因此假设跨域 POST 完全被同源策略禁止。
这就是为什么网络服务器有机会选择接收跨域写入请求的原因。
* 本质上是 AJAX 版本的 CSRF。
不是关于性能的预检请求吗?通过预检请求,客户端可以在发送大量数据之前快速知道该操作是否被允许,例如,在 JSON 中使用 PUT 方法。或者在通过网络传输身份验证标头中的敏感数据之前。
除了自定义标头之外,PUT、DELETE 和其他方法的事实默认情况下是不允许的(它们需要“Access-Control-Request-Methods”和“Access-Control-Request-Headers”的明确许可),听起来就像双重检查一样,因为这些操作可能对用户数据产生更多影响,而不是 GET 请求。所以,听起来像:
“我看到您允许来自 http://foo.example 的跨站点请求,但您确定您将允许 DELETE 请求吗?您是否考虑过这些请求可能对用户数据造成的影响?”
我不理解预检请求与旧服务器优势之间的引用相关性。在 CORS 之前实施或没有 CORS 意识的 Web 服务将永远不会收到任何跨站点请求,因为首先它们的响应将没有“Access-Control-Allow-Origin”标头。
不定期副业成功案例分享