ChatGPT解决这个技术问题 Extra ChatGPT

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

跨域资源共享是一种允许网页向另一个域(来自 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 解决的问题吗?


M
Michael Iles

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

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

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

这里有三种情况:

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


如果是这种情况,为什么每次请求都会发送它?每个服务器的一个请求应该足以确定服务器是否知道 CORS。
该规范在浏览器中包含一个 preflight-result-cache。因此,虽然它仍然感觉笨拙和无效的安全性,但似乎可以配置新服务器以无限期地缓存预检。
我同意预检请求本质上与安全性无关,但听起来 CORS 使用预检请求绝对是出于安全原因。这不仅仅是为了防止相对无害的错误情况的完整性检查。如果用户代理盲目地将请求发送到服务器,错误地假设服务器实现了 CORS,那么它很可能接受跨站点请求伪造。即使 javascript 无法读取响应,服务器也可能已经采取了一些不受欢迎的操作,例如删除帐户或进行银行转帐。
问题是,preflight-result-cache 基本上是无用的,因为 1. 它只适用于确切的请求,而不是整个域,所以无论如何所有请求都会在第一次预检;和 2. 实施时,在大多数浏览器中限制为 10 分钟,因此甚至不会无限期地接近。
@VikasBansal 现有服务器必须“选择加入”并同意通过配置它们如何回复预检选项请求来跨源共享资源。如果他们没有明确响应预检请求,浏览器将不会发出实际请求。毕竟,并非所有服务器都愿意接受跨域请求。
K
Kevin Christopher Henry

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

引入了预检请求,以便浏览器在发送某些请求之前可以确定它正在处理可识别 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 攻击面。

但是 POSTlisted 作为不需要预检的方法。这可以像 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 安全的 methodsheaders。这可能意味着省略您本来应该包含的自定义标头(例如 X-Requested-With)、更改 Content-Type 或更多。

无论你做什么,你都必须确保你有适当的 CSRF 保护,因为 CORS 不会阻止所有不安全的请求。作为原始规范puts it:“简单请求具有非检索意义的资源必须保护自己免受跨站点请求伪造”。


当您说“由于用户已登录 A.com,该请求(如果发送)将包含识别用户的 cookie。”为什么来自选项卡 B 的请求会包含来自选项卡 A 的 cookie,选项卡通常(至少在 Chrome 中)是浏览器中的独立进程?
@Yos:浏览器会包含这些 cookie,因为这就是浏览器的预期工作方式(如 RFC 6265 等标准中所述)。浏览器是否对选项卡使用单独的进程是一个实现细节,它不会阻止它发送 cookie。
@divine:它不是由同一个域触发的,它是从 B.com 的页面由 Javascript 触发的对 A.com 的请求。
@Number945:前面的段落解释了问题。危险在于,如果浏览器允许发送 DELETE,服务器可能会执行它。这就是我们试图通过预检来防止的问题。对于安全请求,我们不必保护服务器,因此在这种情况下不需要预检,浏览器可以仅依靠标头来决定要做什么。
@CiroSantilli棉花新疆TRUMPBANBAD:它会阻止它,因为它是一个DELETE。您链接到的示例使用 POSTGET,通常不会被阻止,我稍后会在答案中讨论。
m
monsur

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

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

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

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


你不能。我的意思是你可以做一个表单 POST,或者用 script/img 做一个 GET。我编辑了这篇文章,希望能澄清这一点。
感谢您的回答,这当然完成了我的照片!不幸的是,我仍然看不到预检背后的中心点。关于您的回答:“意外请求”是什么?在非预检世界中,与预检世界相比(例如丢失预检或简单地“忘记”预检的恶意浏览器),它会如何“更多”出乎意料/更不安全?
可能有一些 API 依赖浏览器的同源策略来保护其资源。它们应该具有额外的安全性,但它们依赖于同源策略。如果没有预检,不同域中的用户现在可以向 API 发出请求。 API 会假设请求是有效的(因为它对 CORS 一无所知)并执行请求。浏览器可能会阻止响应到达用户,但此时,损害可能已经造成。如果请求是 PUT/DELETE,则资源可能已被更新或删除。
@Xiao Peng - ZenUML.com 攻击者将不得不让受害者使用恶意浏览器。如今,大多数人都使用众所周知的安全浏览器。
@Xiao Peng - ZenUML.com 保护两者。如果防止了 xsrf 攻击并且银行因此不删除客户的帐户,那么客户/用户和银行都会感到高兴。
K
Kornel

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

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

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


这应该是公认的答案。这是最明确和最中肯的。本质上,预检请求的唯一要点是将 CORS 之前的 Web 标准与 CORS 之后的 Web 标准集成。
我喜欢这个答案,但我觉得这不是全部原因......“信任假设”必须只适用于只有浏览器才能做的事情(特别是,将浏览器的用户信息发送到他们的域 -即饼干)。如果这不是假设的一部分,那么跨域浏览器请求可以做的任何事情都可以由第三方非浏览器代理完成,对吧?
@FabioBeltramini 对,非浏览器可以发送他们想要的任何东西。但是,通过浏览器进行的攻击是特殊的,因为您可以让其他人的浏览器通过他们自己的 IP、使用他们自己的 cookie 等来做事。
我开始看到真正的问题。感谢@FabioBeltramini 的评论和回复以及 Kronel 的回复。如果没有进行飞行前检查,攻击者可以将一些 JavaScript 代码放在他的站点上,但可以从许多其他人的计算机上执行。所有其他客户都很难“雇佣”其他人来做这件事,包括移动应用程序。
D
Dylan Tack

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

<!-- 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 进行预检没有安全优势。


@MilesRout:Telnet 不是预检旨在减轻的威胁模型的一部分。 Preflight 与以下浏览器相关:1) 依赖于存储的“环境权限”(例如 cookie),以及 2) 可能被第三方欺骗以滥用该权限(例如跨站点请求伪造)。广义模型称为 confused deputy problem
要求对简单的 XHR 进行预检并不是说没有安全优势,而是这样做会违反规则。突然之间,以前有效的 GET 请求不再有效。
H
Hirako

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

场景:

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

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

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


同意@michael-iles 的回复中提到的“新服务器”仍然可能发生的 CSRF 攻击问题。
但是为什么像 POST 这样的带有 Content-Type text/plain 的请求不做飞行前请求呢?在我看来,如果安全是一个问题,每个“写入”请求(POST、PUT、DELETE)都应该有这个飞行前请求。
带有 text/plain 的 POST 被认为是一个简单的请求 - 请注意,如果源不匹配,浏览器将不会显示响应(如果服务器未配置 CORS,则会出现这种情况)。
在攻击方面,可以做一些有趣的事情,利用简单的请求是可以容忍的,并且会被大多数浏览器发送。例如this
@Hirako 如果原始标头值是不同的域,浏览器可能不会显示响应,但仍然会造成损坏。
O
Oliver Weichhold

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

Source


A
AndreKR

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

但是,编写请求(如 POST)会在请求到达网络服务器时造成损坏。*网络服务器可以检查 Origin 标头以确定请求是否合法,但此检查是通常没有实现,因为网络服务器不需要 CORS 或者网络服务器比 CORS 旧,因此假设跨域 POST 完全被同源策略禁止。

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

* 本质上是 AJAX 版本的 CSRF。


“网络服务器比 CORS 更老,因此假设同源策略完全禁止跨域 POST。”始终允许跨域 POST。您只是无法查看响应。 “这就是为什么网络服务器有机会选择接收跨域写入请求。” - 他们只能选择加入 PUT/DELETE 请求(或带有非标准标头的 GET/POST)。无论是否知道 CORS,都将允许一个简单的 POST 请求。
@DavidKlempfner 我没有意识到这一点,我想知道为什么允许这样做。可能是因为不允许它会破坏太多现有的解决方案(例如通过 POST 获得的搜索结果的链接),并且在没有凭据的情况下它被认为是相对安全的。
F
Felipe Nipo Ferreira

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

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

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

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


您误解了 Access-Control-Allow-Origin。缺少该标头并不会阻止浏览器发送请求,它只会阻止 JS 读取响应中的数据。
您能否再次解释一下“缺少该标头并不能阻止浏览器发送请求,它只会阻止 JS 再次读取响应中的数据”,我不完全明白。
@DylanTack 好点。这让我想知道,为什么 GET xhr 也不是预检?虽然不太可能,但 GET 请求也可能是有害的/数据变异的。此外,由于所有这些都可以通过 CSRF 解决,在我看来,这似乎是浏览器过度保护了因疏忽而无法实施常见安全实践的服务器。
接受的答案很好地解释了它,作为“不改变规则的东西”(与 CORS 存在之前创建的网站的向后兼容性)。看到代码仍然很有趣,所以我发布了另一个带有代码示例的答案。