前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >跨域资源共享(CORS)

跨域资源共享(CORS)

作者头像
黑伞安全
发布2019-10-16 12:03:34
3.5K0
发布2019-10-16 12:03:34
举报
文章被收录于专栏:黑伞安全黑伞安全

简单先了解一下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请求的风险。

哪些请求使用CORS?部分

此跨域共享标准可以为以下站点启用跨站点HTTP请求:

  • XMLHttpRequest或提取 API的调用,如上所述。
  • Web字体(用于@font-faceCSS中的跨域字体),以便服务器可以部署TrueType字体,这些字体只能跨站点加载并允许被允许的网站使用。
  • WebGL纹理。
  • 使用绘制到画布上的图像/视频帧drawImage()
  • 图片的CSS形状。

本文是对跨域资源共享的一般讨论,并包括对必要的HTTP标头的讨论。

功能概述部分

跨域资源共享标准的工作原理是添加新的HTTP标头,这些标头允许服务器描述允许哪些来源从Web浏览器读取该信息。此外,对于可能对服务器数据产生副作用的HTTP请求方法(尤其是HTTP方法,而不是GETPOST某些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
  • 除了由用户代理自动设置的标头(例如,ConnectionUser-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浏览器技术预览地方上的值的额外限制AcceptAccept-LanguageContent-Language头。如果这些标头中的任何一个具有“非标准”值,则WebKit / Safari不会将请求视为“简单请求”。没有记录WebKit / Safari认为“非标准”的值,以下WebKit错误除外:

  • 需要对非标准CORS安全列出的请求标头进行飞行前检查接受,接受语言和内容语言
  • 对于简单的CORS,在Accept,Accept-Language和Content-Language请求标头中允许使用逗号
  • 切换到简单CORS请求中受限制的Accept标头的黑名单模型

没有其他浏览器实现这些额外的限制,因为它们不是规范的一部分。

例如,假设Web内容https://foo.example希望调用domain上的内容https://bar.other。此类代码可用于部署在foo.example以下位置的JavaScript中:

代码语言:javascript
复制
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
   
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

这使用CORS标头来处理特权,从而在客户端和服务器之间执行简单的交换:让我们看看在这种情况下浏览器将发送给服务器什么,并让我们看看服务器如何响应:

代码语言:javascript
复制
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

代码语言:javascript
复制
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,则他们将发送:

代码语言:javascript
复制
Access-Control-Allow-Origin: https://foo.example

现在,除了域之外,没有其他域https://foo.example可以跨站点访问资源。该Access-Control-Allow-Origin头应包含在请求的发送的值Origin头。

事前要求部分

与“简单请求”(如上所述)不同,“预检”请求首先通过该OPTIONS方法将HTTP请求发送到另一个域上的资源,以确定实际请求是否可以安全发送。跨站点请求这样被预检,因为它们可能会影响用户数据。

以下是将要进行预检的请求的示例:

代码语言:javascript
复制
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请求中才需要它们。)

让我们看一下客户端和服务器之间的完整交换。第一个交换是预检请求/响应

代码语言:javascript
复制
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

完成预检请求后,将发送实际请求:

代码语言:javascript
复制
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行):

代码语言:javascript
复制
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

Access-Control-Request-Method报头通知服务器作为预检请求被发送的实际请求时,它将被与发送的一部分POST请求方法。的Access-Control-Request-Headers报头通知服务器被发送的实际请求时,它将被与发送X-PINGOTHERContent-Type自定义首部。服务器现在有机会确定在这种情况下是否希望接受请求。

上面的第14-23行是服务器发回的响应,指示请求方法(POST)和请求标头(X-PINGOTHER)是可接受的。特别地,让我们看一下第17-20行:

代码语言:javascript
复制
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,并说POSTGET是可行的方法来查询相关资源。请注意,此标头类似于Allow响应标头,但严格在访问控制的上下文中使用。

服务器还发送Access-Control-Allow-Headers一个值“ X-PINGOTHER, Content-Type”,确认这些是允许与实际请求一起使用的标头。像一样Access-Control-Allow-MethodsAccess-Control-Allow-Headers是逗号分隔的可接受标头列表。

最后,Access-Control-Max-Age给出以秒为单位的值,该值表示对预检请求的响应可以缓存多长时间而无需发送另一个预检请求。在这种情况下,86400秒是24小时。请注意,每个浏览器都有一个最大内部值,当Access-Control-Max-Age较大时,该内部值优先。

预检请求和重定向

并非所有浏览器目前都支持在预检请求后进行以下重定向。如果在预检请求后发生重定向,则当前某些浏览器将报告诸如以下的错误消息。

该请求已重定向到“ https://example.com/foo”,对于需要预检的跨域请求是不允许的

请求需要进行预检,不允许遵循跨域重定向

CORS协议最初要求该行为,但后来更改为不再需要它。但是,并非所有浏览器都实现了此更改,因此仍然表现出最初所需的行为。

因此,在所有浏览器都赶上规范之前,您可以通过执行以下一项或两项操作来解决此限制:

  • 更改服务器端的行为以避免预检和/或避免重定向-如果您可以控制服务器,则将请求发送到
  • 更改请求,使其成为一个不会导致预检的简单请求

但是,如果不可能进行这些更改,那么另一种可能的方法是:

  1. 发出一个简单请求(Response.url用于Fetch API或XMLHttpRequest.responseURL),以确定真正的预检请求最终将到达哪个URL。
  2. 使用从第一步Response.urlXMLHttpRequest.responseURL第一步中获得的URL发出另一个请求(“真实”请求)。

但是,如果请求是由于请求中存在Authorization标头而触发预检的请求,则无法使用上述步骤解决限制。除非您可以控制请求的服务器,否则您将根本无法解决它。

带凭证的请求部分

由两个暴露的最有趣的功能XMLHttpRequest或获取和CORS是使知道的“持证”请求的能力的HTTP cookies和HTTP验证信息。默认情况下,在跨站点XMLHttpRequest或Fetch调用中,浏览器将发送凭据。在调用XMLHttpRequest对象或Request构造函数时,必须设置一个特定的标志。

在此示例中,最初从中加载的内容http://foo.examplehttp://bar.other设置Cookie 的资源发出简单的GET请求。foo.example上的内容可能包含这样的JavaScript:

代码语言: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这是客户端和服务器之间的示例交换:

代码语言:javascript
复制
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)。

第三方Cookie

请注意,在CORS响应中设置的Cookie必须遵守常规的第三方Cookie政策。在上面的示例中,该页面是从加载的foo.example,但是第22行上的cookie是由发送的bar.other,因此如果用户已将其浏览器配置为拒绝所有第三方cookie,则不会保存该cookie。

HTTP响应头部分

本节列出了服务器为跨源资源共享规范定义的访问控制请求发送回的HTTP响应标头。上一节概述了这些功能。

访问控制允许来源部分

返回的资源可能具有一个Access-Control-Allow-Origin标头,其语法如下:

代码语言:javascript
复制
Access-Control-Allow-Origin: <origin> | *

Access-Control-Allow-Origin指定一个来源,告诉浏览器允许该来源访问资源;否则-对于没有凭据的请求-“ *”通配符,告诉浏览器允许任何源访问资源。

例如,要允许源头的代码https://mozilla.org访问资源,可以指定:

代码语言:javascript
复制
Access-Control-Allow-Origin: https://mozilla.org

如果服务器指定的是单个来源而不是*通配符,则服务器也应OriginVary响应标头中包含信息-指示客户端服务器响应将基于Origin请求标头的值而有所不同。

访问控制公开标头部分

Access-Control-Expose-Headers标题即可让所有浏览器都允许访问的服务器白名单头。

代码语言:javascript
复制
Access-Control-Expose-Headers: <header-name>[, <header-name>]*

例如,以下内容:

代码语言:javascript
复制
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

…将允许X-My-Custom-HeaderX-Another-Custom-Header标头公开给浏览器。

访问控制最大年龄部分

Access-Control-Max-Age报头指示预检请求的结果多久可以被缓存。有关预检请求的示例,请参见以上示例。

代码语言:javascript
复制
Access-Control-Max-Age: <delta-seconds>

delta-seconds参数表示结果可以缓存的秒数。

访问控制允许凭证部分

Access-Control-Allow-Credentials报头指示是否对所述请求的响应可以在被暴露credentials标记为真。当用作对预检请求的响应的一部分时,这指示是否可以使用凭据发出实际请求。请注意,简单的GET请求不会被预先处理,因此,如果对具有凭据的资源进行请求,则如果此标头未随资源一起返回,则浏览器将忽略该响应,并且该响应不会返回到Web内容。

代码语言:javascript
复制
Access-Control-Allow-Credentials: true

凭证请求已在上面讨论。

访问控制允许方法部分

Access-Control-Allow-Methods头指定访问资源时所允许的一种或多种方法。用于响应预检请求。上面讨论了请求被预检的条件。

代码语言:javascript
复制
Access-Control-Allow-Methods: <method>[, <method>]*

上面给出了预检请求的示例,其中包括将该标头发送到浏览器的示例。

访问控制允许标题部分

所述Access-Control-Allow-Headers报头在响应用于一个预检请求,以指示在进行实际请求时HTTP标头都可以使用。

代码语言:javascript
复制
Access-Control-Allow-Headers: <header-name>[, <header-name>]*

HTTP请求标头部分

本节列出了客户端在发出HTTP请求时可以使用的标头,以利用跨域共享功能。请注意,在调用服务器时会为您设置这些标头。使用跨站点XMLHttpRequest功能的开发人员不必以编程方式设置任何跨域共享请求标头。

起源部分

Origin报头指示跨站点接入请求或预检请求的来源。

代码语言:javascript
复制
Origin: <origin>

源是指示从中发起请求的服务器的URI。它不包括任何路径信息,而仅包括服务器名称。

注:origin值可以是null或URI。

请注意,在任何访问控制请求中,始终发送Origin标头。

访问控制请求方法部分

Access-Control-Request-Method发出的预检要求,让服务器知道实际的请求时会怎样使用HTTP方法时使用。

代码语言:javascript
复制
Access-Control-Request-Method: <method>

可以在上面找到这种用法的示例。

访问控制请求标头部分

Access-Control-Request-Headers发出的预检要求,让服务器知道什么实际的请求时HTTP标头的时候会用到头使用。

代码语言:javascript
复制
Access-Control-Request-Headers: <field-name>[, <field-name>]*

可以在上面找到这种用法的示例。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑伞攻防实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 哪些请求使用CORS?部分
  • 功能概述部分
  • 访问控制方案的示例部分
    • 简单的要求部分
      • 事前要求部分
        • 预检请求和重定向
      • 带凭证的请求部分
        • 凭证请求和通配符
        • 第三方Cookie
    • HTTP响应头部分
      • 访问控制允许来源部分
        • 访问控制公开标头部分
          • 访问控制最大年龄部分
            • 访问控制允许凭证部分
              • 访问控制允许方法部分
                • 访问控制允许标题部分
                • HTTP请求标头部分
                  • 起源部分
                    • 访问控制请求方法部分
                      • 访问控制请求标头部分
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档