简单先了解一下CORS,方便我们后续去挖一些CORS的漏洞,最近CORS也是比较火的!
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
比如,站点 http://domain-a.com 的某 HTML 页面通过XMLHttpRequest
请求 http://domain-b.com/image.jpg。网络上的许多页面都会加载来自不同域的CSS样式表,图像和脚本等资源。
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。例如,XMLHttpRequest和Fetch API遵循同源策略。这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头
CORS机制支持安全的跨域请求以及浏览器和服务器之间的数据传输。现代浏览器在诸如XMLHttpRequest
或Fetch之类的API中使用CORS 来减轻跨源HTTP请求的风险。
此跨域共享标准可以为以下站点启用跨站点HTTP请求:
XMLHttpRequest
或提取 API的调用,如上所述。@font-face
CSS中的跨域字体),以便服务器可以部署TrueType字体,这些字体只能跨站点加载并允许被允许的网站使用。drawImage()
。本文是对跨域资源共享的一般讨论,并包括对必要的HTTP标头的讨论。
跨域资源共享标准的工作原理是添加新的HTTP标头,这些标头允许服务器描述允许哪些来源从Web浏览器读取该信息。此外,对于可能对服务器数据产生副作用的HTTP请求方法(尤其是HTTP方法,而不是GET
或POST
某些MIME类型的 HTTP方法),该规范要求浏览器“预检”请求,并使用HTTP OPTIONS
请求方法,然后在服务器“批准”后发送实际请求。服务器还可以通知客户端是否应将“凭据”(例如Cookies和HTTP Authentication)与请求一起发送。
CORS故障会导致错误,但是出于安全原因,该错误的详细信息不适用于JavaScript。所有代码都知道发生了错误。确定具体出问题的唯一方法是查看浏览器的控制台以获取详细信息。
随后的部分讨论了方案,并提供了所用HTTP标头的细分。
我们提出了三种方案,这些方案演示了跨域资源共享的工作方式。所有这些示例都使用XMLHttpRequest
,可以在任何支持的浏览器中进行跨站点请求。
您可以在http://arunranga.com/examples/access-control/上找到这些部分中的JavaScript代码片段(以及正在运行的服务器代码实例,这些实例正确处理了这些跨站点请求),并且可以在支持跨站点的浏览器XMLHttpRequest
。
从服务器角度(包括PHP代码段)的跨域资源共享的讨论可以在服务器端访问控制(CORS)文章中找到。
有些请求不会触发CORS的预检。尽管Fetch规范(定义了CORS)未使用该术语,但在本文中将其称为“简单请求”。“简单请求”是满足以下所有条件的请求:
GET
HEAD
POST
Connection
,User-Agent
,或在取规格为“禁止的标题名称”中定义的其它标题),它允许被手动设置仅标头是那些抓取规范定义为“ CORS安全列出的请求标头”,它们是:Accept
Accept-Language
Content-Language
Content-Type
(但请注意下面的其他要求)DPR
Downlink
Save-Data
Viewport-Width
Width
Content-Type
标头唯一允许的值为:application/x-www-form-urlencoded
multipart/form-data
text/plain
XMLHttpRequestUpload
对象上注册事件侦听器;使用XMLHttpRequest.upload
属性访问这些。ReadableStream
请求中未使用任何对象。注意:这些与Web内容已经可以发出的跨站点请求种类相同,除非服务器发送适当的标头,否则不会将响应数据释放给请求者。因此,防止跨站点请求伪造的站点不必担心HTTP访问控制。
注:允许在WebKit每日和Safari浏览器技术预览地方上的值的额外限制Accept
,Accept-Language
和Content-Language
头。如果这些标头中的任何一个具有“非标准”值,则WebKit / Safari不会将请求视为“简单请求”。没有记录WebKit / Safari认为“非标准”的值,以下WebKit错误除外:
没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。
例如,假设Web内容https://foo.example
希望调用domain上的内容https://bar.other
。此类代码可用于部署在foo.example
以下位置的JavaScript中:
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
这使用CORS标头来处理特权,从而在客户端和服务器之间执行简单的交换:让我们看看在这种情况下浏览器将发送给服务器什么,并让我们看看服务器如何响应:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
note的请求标头是Origin
,它表明调用来自https://foo.example
。
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
作为响应,服务器发回Access-Control-Allow-Origin
标头。Origin
标头和标头的使用以Access-Control-Allow-Origin
最简单的方式显示访问控制协议。在这种情况下,服务器将以响应Access-Control-Allow-Origin: *
,这意味着该资源可以被任何域访问。如果资源所有者https://bar.other
希望将对资源的访问限制为仅来自的请求https://foo.example
,则他们将发送:
Access-Control-Allow-Origin: https://foo.example
现在,除了域之外,没有其他域https://foo.example
可以跨站点访问资源。该Access-Control-Allow-Origin
头应包含在请求的发送的值Origin
头。
与“简单请求”(如上所述)不同,“预检”请求首先通过该OPTIONS
方法将HTTP请求发送到另一个域上的资源,以确定实际请求是否可以安全发送。跨站点请求这样被预检,因为它们可能会影响用户数据。
以下是将要进行预检的请求的示例:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('Ping-Other', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
上面的示例创建一个XML正文以与POST
请求一起发送。此外,设置了非标准的HTTP Ping-Other
请求标头。此类标头不是HTTP / 1.1的一部分,但通常对Web应用程序有用。由于该请求使用的Content-Type为application/xml
,并且由于设置了自定义标头,因此该请求被预检。(注意:如下所述,实际的POST
请求不包括Access-Control-Request-*
标头;仅在OPTIONS
请求中才需要它们。)
让我们看一下客户端和服务器之间的完整交换。第一个交换是预检请求/响应:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
完成预检请求后,将发送实际请求:
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
上面的1-11行代表该OPTIONS
方法的预检请求。浏览器根据上面的JavaScript代码段所使用的请求参数确定是否需要发送此请求,以便服务器可以响应是否可以使用实际请求参数发送请求。OPTIONS是一种HTTP / 1.1方法,用于确定来自服务器的更多信息,并且是一种安全的方法,这意味着它不能用于更改资源。请注意,与OPTIONS请求一起,还发送了另外两个请求标头(分别是第10行和第11行):
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
的Access-Control-Request-Method
报头通知服务器作为预检请求被发送的实际请求时,它将被与发送的一部分POST
请求方法。的Access-Control-Request-Headers
报头通知服务器被发送的实际请求时,它将被与发送X-PINGOTHER
和Content-Type
自定义首部。服务器现在有机会确定在这种情况下是否希望接受请求。
上面的第14-23行是服务器发回的响应,指示请求方法(POST
)和请求标头(X-PINGOTHER
)是可接受的。特别地,让我们看一下第17-20行:
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
服务器响应Access-Control-Allow-Methods
,并说POST
和GET
是可行的方法来查询相关资源。请注意,此标头类似于Allow
响应标头,但严格在访问控制的上下文中使用。
服务器还发送Access-Control-Allow-Headers
一个值“ X-PINGOTHER, Content-Type
”,确认这些是允许与实际请求一起使用的标头。像一样Access-Control-Allow-Methods
,Access-Control-Allow-Headers
是逗号分隔的可接受标头列表。
最后,Access-Control-Max-Age
给出以秒为单位的值,该值表示对预检请求的响应可以缓存多长时间而无需发送另一个预检请求。在这种情况下,86400秒是24小时。请注意,每个浏览器都有一个最大内部值,当Access-Control-Max-Age
较大时,该内部值优先。
并非所有浏览器目前都支持在预检请求后进行以下重定向。如果在预检请求后发生重定向,则当前某些浏览器将报告诸如以下的错误消息。
该请求已重定向到“ https://example.com/foo”,对于需要预检的跨域请求是不允许的
请求需要进行预检,不允许遵循跨域重定向
CORS协议最初要求该行为,但后来更改为不再需要它。但是,并非所有浏览器都实现了此更改,因此仍然表现出最初所需的行为。
因此,在所有浏览器都赶上规范之前,您可以通过执行以下一项或两项操作来解决此限制:
但是,如果不可能进行这些更改,那么另一种可能的方法是:
Response.url
用于Fetch API或XMLHttpRequest.responseURL
),以确定真正的预检请求最终将到达哪个URL。Response.url
或XMLHttpRequest.responseURL
第一步中获得的URL发出另一个请求(“真实”请求)。但是,如果请求是由于请求中存在Authorization
标头而触发预检的请求,则无法使用上述步骤解决限制。除非您可以控制请求的服务器,否则您将根本无法解决它。
由两个暴露的最有趣的功能XMLHttpRequest
或获取和CORS是使知道的“持证”请求的能力的HTTP cookies和HTTP验证信息。默认情况下,在跨站点XMLHttpRequest
或Fetch调用中,浏览器将不发送凭据。在调用XMLHttpRequest
对象或Request
构造函数时,必须设置一个特定的标志。
在此示例中,最初从中加载的内容http://foo.example
向http://bar.other
设置Cookie 的资源发出简单的GET请求。foo.example上的内容可能包含这样的JavaScript:
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
第7行显示了XMLHttpRequest
必须设置的标志才能使用Cookies进行调用,即withCredentials
布尔值。默认情况下,调用是在不使用Cookie的情况下进行的。由于这是一个简单的GET
请求,因此不会进行预检,但是浏览器将拒绝任何没有标题的响应,并且不会使响应可用于调用Web内容。Access-Control-Allow-Credentials: true
这是客户端和服务器之间的示例交换:
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
尽管第11行包含发往其中的内容的Cookie http://bar.other
,但如果bar.other没有以(第19行)进行响应,则该响应将被忽略并且不能用于Web内容。Access-Control-Allow-Credentials: true
响应凭据请求时,服务器必须在Access-Control-Allow-Origin
标头的值中指定一个来源,而不是指定*
通配符。
因为上面示例中的请求标头包含Cookie
标头,所以如果Access-Control-Allow-Origin
标头的值为“ *” ,则请求将失败。但这不会失败:因为Access-Control-Allow-Origin
标头的值是“ http://foo.example
”(实际来源)而不是“ *
”通配符,所以凭据识别内容将返回到正在调用的Web内容。
请注意,Set-Cookie
上面示例中的响应头也设置了另一个cookie。如果发生故障,则会引发一个异常(取决于所使用的API)。
请注意,在CORS响应中设置的Cookie必须遵守常规的第三方Cookie政策。在上面的示例中,该页面是从加载的foo.example
,但是第22行上的cookie是由发送的bar.other
,因此如果用户已将其浏览器配置为拒绝所有第三方cookie,则不会保存该cookie。
本节列出了服务器为跨源资源共享规范定义的访问控制请求发送回的HTTP响应标头。上一节概述了这些功能。
返回的资源可能具有一个Access-Control-Allow-Origin
标头,其语法如下:
Access-Control-Allow-Origin: <origin> | *
Access-Control-Allow-Origin
指定一个来源,告诉浏览器允许该来源访问资源;否则-对于没有凭据的请求-“ *
”通配符,告诉浏览器允许任何源访问资源。
例如,要允许源头的代码https://mozilla.org
访问资源,可以指定:
Access-Control-Allow-Origin: https://mozilla.org
如果服务器指定的是单个来源而不是*
通配符,则服务器也应Origin
在Vary
响应标头中包含信息-指示客户端服务器响应将基于Origin
请求标头的值而有所不同。
该Access-Control-Expose-Headers
标题即可让所有浏览器都允许访问的服务器白名单头。
Access-Control-Expose-Headers: <header-name>[, <header-name>]*
例如,以下内容:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
…将允许X-My-Custom-Header
和X-Another-Custom-Header
标头公开给浏览器。
的Access-Control-Max-Age
报头指示预检请求的结果多久可以被缓存。有关预检请求的示例,请参见以上示例。
Access-Control-Max-Age: <delta-seconds>
该delta-seconds
参数表示结果可以缓存的秒数。
的Access-Control-Allow-Credentials
报头指示是否对所述请求的响应可以在被暴露credentials
标记为真。当用作对预检请求的响应的一部分时,这指示是否可以使用凭据发出实际请求。请注意,简单的GET
请求不会被预先处理,因此,如果对具有凭据的资源进行请求,则如果此标头未随资源一起返回,则浏览器将忽略该响应,并且该响应不会返回到Web内容。
Access-Control-Allow-Credentials: true
凭证请求已在上面讨论。
该Access-Control-Allow-Methods
头指定访问资源时所允许的一种或多种方法。用于响应预检请求。上面讨论了请求被预检的条件。
Access-Control-Allow-Methods: <method>[, <method>]*
上面给出了预检请求的示例,其中包括将该标头发送到浏览器的示例。
所述Access-Control-Allow-Headers
报头在响应用于一个预检请求,以指示在进行实际请求时HTTP标头都可以使用。
Access-Control-Allow-Headers: <header-name>[, <header-name>]*
本节列出了客户端在发出HTTP请求时可以使用的标头,以利用跨域共享功能。请注意,在调用服务器时会为您设置这些标头。使用跨站点XMLHttpRequest
功能的开发人员不必以编程方式设置任何跨域共享请求标头。
的Origin
报头指示跨站点接入请求或预检请求的来源。
Origin: <origin>
源是指示从中发起请求的服务器的URI。它不包括任何路径信息,而仅包括服务器名称。
注:该origin
值可以是null
或URI。
请注意,在任何访问控制请求中,始终发送Origin
标头。
该Access-Control-Request-Method
发出的预检要求,让服务器知道实际的请求时会怎样使用HTTP方法时使用。
Access-Control-Request-Method: <method>
可以在上面找到这种用法的示例。
该Access-Control-Request-Headers
发出的预检要求,让服务器知道什么实际的请求时HTTP标头的时候会用到头使用。
Access-Control-Request-Headers: <field-name>[, <field-name>]*
可以在上面找到这种用法的示例。