知乎:Sp4rkW
GITHUB:Sp4rkW
B站:一只技术君
博客:https://sp4rkw.blog.csdn.net/
联系邮箱:getf_own@163.com
文章目录
前两周在90sec论坛上看到一篇挺有意思的文章,主要讲的是同源策略的问题,作者写的较为详细,其中有些内容也解决了我之前的一些疑问。但是,看百次不如自己动手去做一次,此外,b站也好久没有更新技术视频了。于是我决定去复现一下这篇文章的所有实验,并结合自己的一些经验,来聊一聊可能存在的一些漏洞该怎么挖掘与防御。视频的完整内容已经上传b站,各位看官可以移步观看,有账号的同学能给个一键三连就再好不过了~
漏洞挖掘中的同源策略 P1
漏洞挖掘中的同源策略 P2
PS:第三期视频因为我误删了环境虚拟机,懒得配置了,就没有做了,直接看文章吧~
一、同源策略
1、同源策略的定义
同源策略限制从一个源加载的文档或脚本与另一个源的资源进行交互,是用于隔离潜在恶意文件的重要机制。不同源的客户端脚本在没有明确授权的情况下,不能读取对方资源。比如,浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。
2、什么是同源
同源要求三个都相同:
- 端口
- IP
- 协议
URL | 说明 | 是否允许通信 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js | 同一域名下 | 允许 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js | 同一域名下不同文件夹 | 允许 |
http://www.a.com:8000/a.js http://www.a.com/b.js | 同一域名,不同端口 | 不允许 |
http://www.a.com/a.js https://www.a.com/b.js | 同一域名,不同协议 | 不允许 |
http://www.a.com/a.js http://70.32.92.74/b.js | 域名和域名对应ip | 不允许 |
http://www.a.com/a.js http://script.a.com/b.js | 主域相同,子域不同 | 不允许 |
http://www.cnblogs.com/a.js 1 http://www.a.com/b.js | 不同域名 | 不允许 |
3、同源策略在保护什么
同源策略对于js的限制有3点:
(1)无法用js读取非同源的Cookie、LocalStorage 和 IndexDB 无法读取
为了防止恶意网站通过js获取用户其他网站的cookie。
(2) 无法用js获取非同源的DOM
如果没有这一条,恶意网站可以通过iframe打开银行页面,可以获取dom就相当于可以获取整个银行页面的信息。当然,iframe标签允许加载,可能会造成另外一个问题,点击劫持。点击劫持这里我就不解释了,详情见我之前的视频
https://www.bilibili.com/video/BV1i4411C7WP/
(3) AJAX 请求限制发送
为什么要做这个请求限制呢?比如说我搭建了一个站点,我在我的页面中写入js请求,去请求百度修改账号头像。如果没有跨域的保护,我可以发送这个请求,并且你如果百度账号是登录状态,那你的头像就被我修改了。
二、环境搭建
1、服务器配置
名称 | 系统 | IP 地址 |
---|---|---|
A | ubuntu server18 | 192.168.1.113 |
B | ubuntu server18 | 192.168.1.115 |
更加详细的配置如图所示:
2、宝塔站点安装配置
之后直接给两个服务器安装宝塔,方便快速搭建php站点
wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh
安装完成之后选择一套集成环境安装一下即可,我的话就选择了nginx这一套,ftp不需要,所以就没有安装了,传输文件可以使用winscp来解决
之后创建个站点
为了方便对比,我总共创建了三个web站点,分别是:
ip | 端口 | 服务类型 | 协议 |
---|---|---|---|
192.168.1.113 | 80 | 基于php的web站点 | http |
192.168.1.115 | 80 | 基于php的web站点 | http |
192.168.1.115 | 81 | 基于php的web站点 | http |
至此环境配置完成
三、同源策略限制js演示
原文作者总共给出了三个实验,分别是:
- 基于iframe标签引入跨域站点页面的dom获取cookie
- 测试ajax在不同同源策略下的表现
- 基于iframe标签引入的页面的dom修改
由于第一个和第三个实验本质上是相同的,所以我合并起来进行演示
1、基于iframe引入页面的dom操作
构造两个页面,用于测试,首先我们来看引入页面的代码:
<!-- js-iframe-115.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-iframe-115</title>
</head>
<body>
<iframe id="myframe" src="http://192.168.1.115/js-iframe-115.php"> </iframe>
<h2 id="A">使用iframe标签测试跨域访问非同源网站获取cookie测试</h2>
<input type="button" onclick="changeStyle_1()" value="获取嵌入网站的cookie">
<script>
document.cookie = "ceshi6";
function changeStyle_1() {
var x = document.getElementById("myframe");
var y = (x.contentWindow || x.contentDocument);
if (y.document) y = y.document;
alert(y.cookie);
}
</script>
<h2 id="A">使用iframe标签测试跨域修改背景色</h2>
<input type="button" onclick="changeStyle_2()" value="修改背景颜色">
<script>
function changeStyle_2(){
var x = document.getElementById("myframe");
var y = (x.contentWindow || x.contentDocument);
if (y.document) y = y.document;
y.body.style.backgroundColor = "#008800";
}
</script>
</body>
</html>
上面的两个部分,都是用iframe标签引入了http://192.168.1.115/js-iframe-115.php
页面,作用分别是,提取这个站点的cookie与修改页面背景色。再来看下被引入页面的代码:
<!-- js-iframe-115.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-iframe-115-php</title>
<style>
</style>
</head>
<body>
<div class="container">
<p>用于测试跨域读取cookie</p>
</div>
</body>
</html>
<?php
setcookie('name','king',time()+3600);
setcookie('mail','muke@imooc.com',time()+3600);
setcookie('addr','beijing',time()+3600);
?>
这里的代码比较简单,仅仅是设置cookie。接下来我们进行三组对照实验,我们将php文件放在115站点的80端口web服务下,之后在三个web服务中都放入html文件,这样我们就可以分别从同ip同端口,同ip不同端口,不同ip来进行测试。
1.1、相同ip相同端口
演示的动图如下:
可以看到在同源的情况下,我们可以很容易的通过js获取iframe标签引入的站点的dom,进而获取到cookie与修改页面背景色
1.2、不同ip
演示的动图如下:
可以看到在不同ip也就是不同源的情况下,js控制台会产生警告,当我们执行js事件企图获取到引入站点的dom的时候,控制台直接报错
1.3、相同ip不同端口
演示的动图如下:
对比不同ip的情况,我们可以发现,在不同端口的情况下,控制台没有告警,但是执行js的时候,依旧会报错,无法执行
这时候,问题来了,原文作者在这里实验出了一个非常有趣的结果
就这个结果,我特地请教了原文作者,这里也非常感谢原文作者愿意和我沟通交流。最终我们发现,第一,这个图有问题,alert
的是document.cookie
,也就是当前站点的地址,所以会弹窗;第二,为什么会弹出设置在另一个端口对应web服务的cookie呢?这里就不得不提一个非常有意思的机制了
根据同源策略,cookie是区分端口的,但是浏览器实现来说,“cookie区分域,而不区分端口,也就是说,同一个ip下的多个端口下的cookie是共享的!“
演示动图如下:
进一步搜索这个问题,我发现很多开发都在吐槽这个点,本地测试的时候非常恶心,cookie混乱。所以大部分开发在测试过程中都会给不同端口,同一ip的web服务分别分配不同的子域名,如test1.x.com,test2.x.com。这也就是为什么不同子域名明明对应ip是一个,但是还会被浏览器跨域策略所限制。
这里其实引起了我的另一个思考,如果能找到一个站点的xss,同时获取了服务器的真实ip,那么我可以通过这个xss,拿到同ip不同端口的其他服务的cookie,这个思路有待之后实战测试一下。不过可能最大的限制在于,用户应该都是通过域名进行访问操作的,不过在特定站点都还有一些是通过ip直接访问
2、ajax跨域操作
首先构建两个用于测试的页面,我们先来看请求端的页面:
<!-- js-ajax-115.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-ajax-115</title>
</head>
<body>
<p>请求失败<p>
<script>
var url = "http://192.168.1.115/js-ajax-115.php";
function createXMLHttpRequest() {
if (window.XMLHttpRequest) {
XMLHttpR = new XMLHttpRequest();
} else if (window.ActiveXObject) {
try {
XMLHttpR = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
XMLHttpR = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {}
}
}
}
function sendRequest(url) {
createXMLHttpRequest();
XMLHttpR.open("GET", url, true);
XMLHttpR.setRequestHeader("Content-Type", "text/html;charset=utf-8");
XMLHttpR.onreadystatechange = processResponse;
XMLHttpR.send(null);
}
function processResponse() {
if (XMLHttpR.readyState == 4 && XMLHttpR.status == 200) {
document.write(XMLHttpR.responseText);
}
}
sendRequest(url);
</script>
</body>
</html>
这个页面总共有三个样式,我们结合被请求页面代码的来看
<!-- js-ajax-115.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js-ajax-115-php</title>
<style>
</style>
</head>
<body>
<div class="container">
<?php
if ($_COOKIE['user'] == 'test'){
echo "<p>cookies满足,跨域请求页面测试</p>";
}else{
echo "<p>cookies不满足,跨域请求页面测试</p>";
}
?>
</div>
</body>
</html>
在请求页面被加载的时候,如果js里面的ajax请求加载失败,会显示请求失;js执行成功却没携带cookie,则返回cookies不满足;js执行成功且携带cookie,返回cookies满足。下面我们来进行测试,将php文件放入115的默认80web服务下,html分别放入113,115默认的80web服务下,并手动给115站点赋予cookie键值对user-test。
2.1、相同ip
演示动图如下:
可以看到,在同源的情况下,ajax的请求发送成功,且请求时正常携带了cookie
2.2、不同ip
演示动图如下:
可以看到这里的network对115服务请求有一个报错Provisional headers are shown
,这个报错大部分都是因为跨域问题所引起的,这个报错代表浏览器阻止了ajax请求的发送。原文作者在这里提出了一个观点,说请求成功发送了,但是接收方拒绝接受了
换成我这个实验,意思就是说113服务器的ajax成功执行并带回来数据,但是115并没有接受。这和我的实验结果再一次有所出入,于是我也用burp进行了测试。
可以看到,我捕获到了两个流量,第一个是113的正常请求,第二个就是115的请求。但,需要注意的是,这里对115的请求并不是GET/POST,而是OPTIONS。那,什么是OPTIONS请求呢?
在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。
注意,OPTIONS请求是浏览器给我们强行发送的,当浏览器通过服务器的返回包发现,服务器不接受你的跨域请求,于是接下来的实际跨域请求直接就不会发送了。
四、实际挖掘漏洞中可能遇到的跨域漏洞以及防御措施
原作者的第四章是关于跨域策略在开发中的规避措施,规避措施实际上指的就是这里我结合我自己的挖洞经验,来演示一下一些漏洞的产生原因和修复手段。修复手段可能远不止这些,但是我在实际漏洞挖掘中,碰到这些情况可能我一般就直接放弃了跨域漏洞的挖掘。
1、跨域写漏洞
提前说明一下,不做csrf漏洞基本原理的详细演示了,如果下面的简单演示没有看懂的话,建议可以先去了解一下csrf再继续看后续内容。
首先我没来构建一个简单演示环境,后端服务器代买如下:
<!-- csrf-115.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>csrf-115-php</title>
<style>
</style>
</head>
<body>
<div class="container">
<?php
header('content-type:text/html;charset=utf8');
header('Access-Control-Allow-Origin:192.168.1.111');
header('Access-Control-Allow-Methods:POST');
header('Access-Control-Allow-Headers:x-requested-with,content-type,Accept');
$myfile = fopen("demo.txt", "w");
if ($_COOKIE['user'] == 'test'){
$txt = "cookies满足 ".$_POST['data'];
fwrite($myfile, mb_convert_encoding( $txt, 'UTF-8', mb_detect_encoding($txt)));
}else{
$txt = "cookies不满足 ".$_POST['data'];
fwrite($myfile, mb_convert_encoding( $txt, 'UTF-8', mb_detect_encoding($txt)));
}
fclose($myfile);
?>
</div>
</body>
</html>
这个后端对应的前端页面我直接忽略没写了,按正常逻辑,前端有一个表单,示意图如下:
这一块设计的正常逻辑是,前端表单填写数据,后端php处理post传输过来的data数据,如果有cookie,则写入本地,并备注cookie满足;如果没有cookie,也写入本地文件,只不过会标注cookie不满足。按照漏洞挖掘的思路来说,这里我们首先测试的就是csrf漏洞,使用burp辅助构造poc页面如下:
<!-- csrfpoc.html -->
<html>
<body>
<script>
history.pushState('', '', '/')
</script>
<form action="http://192.168.1.115/csrf-115.php" method="POST">
<input type="hidden" name="data" value="123456" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
将php文件放在115服务器上,将html文件放在113服务器上(这里我直接忽略同ip的情况了,这就是正常的前后端页面,肯定可以执行),按照第三步得出的结论,我们会受到浏览器同源策略的限制。115的地址还是一样,我已经手动给了user-test键值对的cookie。演示动图如下:
再来看下我们服务器的本地文件:
可以看到,我成功构造了一次csrf攻击,同时可以看到同源策略限制的192.168.1.111完全没有任何作用,origin就是192.168.1.115。但完全没有受到影响,因为同源策略限制的是js,不限制表单。那这里应该怎么防御呢?三种较为简单的思路:
- 在表单中增加一个csrf-token参数
- 服务端校验referer来源
- 将前端数据由表单发送修改为使用ajax发送的json类型数据,后端强校验json类型
前两个防御措施很容易理解,我们来具体聊一聊第三个防御措施。这个防御测试基于两个原理:第一,单纯依靠html表单无法发送json类型数据;第二,js会被同源策略所限制。下面我们来看更具体的例子
2、基于json格式的跨域写防御
首先,我们基于上一个实验,修改后端处理代码为接受json格式数据,并强检验Content-Type
头需要设置为application/json
,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>csrf-strict-json</title>
<style>
</style>
</head>
<body>
<div class="container">
<?php
header('Access-Control-Allow-Origin:http://192.168.1.113');
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Headers:content-type');
header('Access-Control-Allow-Methods:*');
if($_SERVER["CONTENT_TYPE"] == "application/json"){
$post_array=$GLOBALS['HTTP_RAW_POST_DATA'];
//--解析Json,获取对应的变量值
$obj=json_decode($post_array,TRUE);
$data = $obj['data'];
echo $data;
$myfile = fopen("demo.txt", "w");
if ($_COOKIE['user'] == 'test'){
$txt = "cookies满足 ".$data;
fwrite($myfile, mb_convert_encoding( $txt, 'UTF-8', mb_detect_encoding($txt)));
}else{
$txt = "cookies不满足 ".$data;
fwrite($myfile, mb_convert_encoding( $txt, 'UTF-8', mb_detect_encoding($txt)));
}
fclose($myfile);
}else{
echo "不接收该类型传参";
}
?>
</div>
</body>
</html>
因为做了强校验,所以使用burp直接构造的poc已经没有办法利用了。关于为什么无法利用,浏览器在很老的版本之前就已经开始禁用html表单发送json数据了,这里不做过多说明。那此时是不是安全了呢?并不是,我们可以通过js来发送json格式数据,构造页面如下:
<html>
<head>
<script style="text/javascript">
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "http://192.168.1.115/csrf-strict-json.php", true);
xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.withCredentials = true;
xhr.send(JSON.stringify({
"data": "json12300"
}));
}
</script>
</head>
<body>
<form action="#">
<input type="button" value="Submit request" onClick="submitRequest()"/>
</form>
</body>
</html>
演示动图如下:
服务器的本地文件如下:
上图证明,可以利用js跨域再一次证明存在csrf漏洞,但我们需要注意一点,这里我将115的web服务跨域保护对113服务器解除了,并且允许跨域请求且携带cookie。
header('Access-Control-Allow-Origin:http://192.168.1.113');
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Headers:content-type');
header('Access-Control-Allow-Methods:*');
这四行的代码的意思分别是:允许来自113的跨域请求,允许跨域请求携带cookie,允许跨域option预检验,接受一切请求类型数据包
相关的演示过程有点多,不继续截动图了,有兴趣的请自行验证这几行代码的作用
3、Access-Control-Allow-Origin:*是否存在漏洞
简单的修改一下上一个实验的代码为
header('Access-Control-Allow-Origin:*');
之后,我们重复上一个实验,演示动图如下:
可以看到我们这次被跨域保护给拦截了,用burp抓包看一下
可以看到,是允许所有域进行跨域的*,那为什么这里的option预检验确失败了呢?
https://fetch.spec.whatwg.org/#resource-requests
翻译一下,简单的意思就是说,如果是*,则不允许携带cookie,我们再来修改下poc页面的代码
// xhr.withCredentials = true;
加上注释,表明不携带cookie,演示动图如下:
服务器的本地文件如下:
可以看到,这次成功跨域了,但是cookie没有携带。综上,我们可以认为,跨域策略为*的时候其实并没有实际危害性,因为跨域请求不可以携带cookie,这基本上不会执行任何敏感操作。
4、跨域读漏洞
跨域读主要有两种表现:
- cors保护机制存在问题,仅仅通过csrftoken保护了存在写漏洞的地方
- jsonp跨域读取
cors读的实验不演示了,参考之前的通过iframe引入页面的dom操作,这就是漏洞其中的一种表现。jsonp跨域的话,我前年的一篇博客已经做了较为详细的演示了,详见:从Ajax聊一聊Jsonp hijacking
5、跨域问题的防御
结合我平时的经验,一般的防御措施以及可能的问题如下:
1、CORS跨域机制完善+所有接口强制POST Json格式
这个说起来很简单,做起来其实挺难的,随着业务的拓展,很多接口开发一不注意,为了方便就直接改了CORS的保护机制,而且一般CORS的保护机制是单独一个模块,引用对整个站点生效的。所以仅仅依靠CORS机制,很容易出问题。
2、CSRF-Token保护机制
这个对付跨域写漏洞很好用,但是跨域读,很容易忽略保护
3、Referer检测机制
这个我个人认为是最简单,但应用起来最方便的一个保护机制了。但是如果存在比如xss漏洞,url重定向问题,甚至referer检验不严格等等,这个机制也就完全失效了
4、自定义header头
跨域的时候header头是不可以控制的,当然,如果服务器的cors策略接收这个header,还是可以继续跨域操作的
不同机制适用于不同场景,结合起来可能会有更好的效果。
文章到此结束,暂时没想到还有什么没有写的了,如果你有任何问题,欢迎与我进行交流~