跨源资源共享是一种机制,它允许一个web页面向另一个域(来自wikipedia)进行XMLHttpRequests。
过去几天我一直在摆弄CORS,我想我已经很好地理解了一切是如何工作的。
因此,我的问题不是关于CORS/preflight如何工作,而是关于提出preflights作为一种新的请求类型的原因。我看不出为什么服务器A需要向服务器B发送预请求(PR)来确定真实请求(RR)是否会被接受--B当然可以在没有任何预请求的情况下接受/拒绝RR。
在搜索了很多之后,我在www.w3.org(7.1.5):
为了保护资源不受在此规范存在之前不能源自某些用户代理的跨源请求的影响,发出一个预飞行请求以确保资源知道此规范。
我发现这是有史以来最难理解的句子。我的解释(最好称之为“最佳猜测”)是,它是关于保护服务器B不受来自不知道规范的服务器C的请求的影响。
谁能解释一个情景/展示一个PR+RR比RR单独解决得更好的问题吗?
我花了一段时间对飞行前请求的目的感到困惑,但我想我现在明白了。
关键的见解是,飞行前请求不是一个安全问题。相反,他们是一个不改变规则的东西。
飞行前请求与安全性无关,并且它们与现在正在开发的应用程序无关,这些应用程序具有CORS的意识。相反,preflight机制有利于那些开发时不知道CORS的服务器,它的功能是在客户端和服务器之间进行健全性检查,以确保它们都知道CORS。CORS的开发人员认为,有足够多的服务器依赖于这样的假设,即他们永远不会收到跨域删除请求,因此他们发明了一种预飞行机制,允许双方选择加入。他们认为,如果只是简单地启用跨域调用,将会破坏太多现有的应用程序。
这里有三种场景:
>
旧服务器,不再在开发中,并且在CORS之前开发。这些服务器可能会假设它们永远不会收到例如跨域删除请求。这种情况是飞行前机制的主要受益者。是的,这些服务可能已经被恶意或不符合要求的用户代理滥用(CORS并没有改变这一点),但是在有CORS的世界中,预飞行机制提供了额外的“健全性检查”,这样客户端和服务器就不会因为web的底层规则发生了变化而中断。
仍在开发中,但包含大量旧代码的服务器,并且不可能/不希望审计所有旧代码以确保其在跨域世界中正常工作。此方案允许服务器逐步选择加入COR,例如,通过说“现在我将允许这个特定的报头”,“现在我将允许这个特定的HTTP动词”,“现在我将允许发送cookies/auth信息”等。此方案受益于预飞行机制。
使用CORS编写的新服务器。根据标准的安全实践,服务器必须在面对任何传入请求时保护其资源--服务器不能信任客户端不做恶意的事情。此场景没有受益于预飞行机制:预飞行机制没有为已正确保护其资源的服务器带来额外的安全性。
引入飞行前请求背后的动机是什么?
引入了飞行前请求,以便浏览器在发送某些请求之前可以确定它正在处理CORS感知服务器。这些请求被定义为既有潜在危险(正在改变状态)又有新的请求(由于相同的起源策略,在CORS之前不可能)。使用预飞行请求意味着服务器必须选择(通过正确地响应预飞行)加入CORS所允许的新的,潜在危险的请求类型。
这就是规范这一部分的含义:“为了保护资源不受在此规范存在之前不能来自某些用户代理的跨源请求的影响,发出了一个预飞行请求,以确保资源知道此规范。”
你能给我举个例子吗?
让我们假设一个浏览器用户登录到他们的银行站点a.com
。当它们导航到恶意的b.com
时,该页面包含一些Javascript,试图向a.com/account
发送delete
请求。由于用户登录到a.com
,因此如果发送该请求,则该请求将包括标识用户的cookie。
在CORS之前,浏览器的同源策略会阻止它发送这个请求。但是,由于CORS的目的只是为了使这种跨源交流成为可能,那就不再合适了。
浏览器可以简单地发送delete
并让服务器决定如何处理它。但是如果a.com
不知道CORS协议呢?它可能继续执行危险的delete
。它可能假定--由于浏览器的相同起源策略--它永远无法接收到这样的请求,因此它可能永远无法抵御这样的攻击。
为了保护这种非CORS感知的服务器,协议要求浏览器首先发送一个预飞行请求。只有支持CORS的服务器才能正确响应这种新的请求,从而允许浏览器知道发送实际的delete
是否安全。
为什么对浏览器大惊小怪,难道攻击者就不能从他们自己的计算机上发送一个delete
请求吗?
当然,但是这样的请求不会包括用户的cookie。这种设计用来防止的攻击依赖于这样一个事实,即浏览器将与请求一起发送其他域的cookie(特别是用户的身份验证信息)。
这听起来像是跨站点请求伪造,站点b.com
上的表单可以用用户的cookiesposit
发送到a.com
并造成破坏。
这就对了。另一种说法是,创建预飞行请求是为了不增加非CORS感知服务器的CSRF攻击面。
但是查看不需要预取灯的“简单”请求的需求,我看到post
仍然是允许的。可以像delete
一样改变状态和删除数据!
那是真的!CORS不能保护您的站点免受CSRF攻击。同样,如果没有CORS,您也无法免受CSRF攻击。预飞行请求的目的只是限制您的CSRF暴露于预CORS世界中已经存在的内容。
叹息。好吧,我勉强接受飞行前的要求。但是为什么我们要为服务器上的每个资源(URL)做呢?服务器要么处理COR,要么不处理。
你确定吗?多个服务器处理单个域的请求并不少见。例如,对a.com/url1
的请求可能由一种服务器处理,而对a.com/url2
的请求则由另一种服务器处理。通常情况下,处理单个资源的服务器不能对该域上的所有资源做出安全保证。
好吧。让我们妥协吧。让我们创建一个新的CORS头,它允许服务器确切地声明它可以为哪些资源说话,这样就可以避免对那些URL的额外的飞行前请求。
好主意!事实上,标题access-control-policy-path
就是为了这个目的而提出的。然而,最终它被排除在规范之外,显然是因为某些服务器错误地实现了URI规范,使得对浏览器看来安全的路径的请求在损坏的服务器上实际上并不安全。
这是一个谨慎的决定吗?它将安全性置于性能之上,允许浏览器立即实现CORS规范而不将现有服务器置于危险之中?或者,仅仅为了在特定的时间内容纳特定服务器中的错误,就让互联网浪费带宽和加倍的延迟,这是不是目光短浅?
众说纷纭。
至少浏览器会缓存单个URL的预运行?
是的。虽然可能不会太久。在WebKit浏览器中,当前最大的预缓存时间为10分钟。
叹息。好吧,如果我知道我的服务器是CORS感知的,因此不需要飞行前请求提供的保护,有什么方法可以让我避免它们吗?
您唯一真正的选择是确保您满足“简单”请求的要求。这可能意味着省略本来会包含的自定义头(如x-requested-with
),隐瞒content-type
或更多内容。
无论您做什么,您都必须确保您有适当的CSRF保护,因为CORS规范没有处理拒绝“简单”请求的问题,包括不安全的POST
。正如规范所说:“对于那些简单请求具有重要意义而非检索意义的资源,必须保护自己免受跨站点请求伪造”。
考虑CORS之前的跨域请求世界。您可以执行标准表单POST,或者使用script
或image
标记来发出GET请求。除了GET/POST之外,您不能创建任何其他请求类型,并且您不能对这些请求发出任何自定义标头。
随着CORS的出现,规范的作者面临着在不破坏Web现有语义的情况下引入新的跨域机制的挑战。他们选择通过给服务器一种选择加入任何新请求类型的方式来做到这一点。这个选择是飞行前的请求。
因此,没有任何自定义头的GET/POST请求不需要预飞,因为这些请求在CORS之前就已经可能了。但是任何带有自定义头的请求,或者put/delete请求,都需要预先处理,因为这些都是CORS规范中的新内容。如果服务器对CORS一无所知,那么它将在没有任何CORS特定标头的情况下进行回复,实际的请求将不会发出。
如果没有预飞行请求,服务器可能开始看到来自浏览器的意外请求。如果服务器没有为这些类型的请求做好准备,这可能会导致安全问题。CORS preflight允许以安全的方式将跨域请求引入web。