内网和无凭证的 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 源由三个部分组成: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
的 Access-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-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
。否则,浏览器将不允许访问响应。
使用通配符放宽 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 攻击的可能性或加剧其影响。