最近在挖业务漏洞的时候想起了之前看到过的JSONP劫持,CORS跨域漏洞。这种涉及敏感信息的漏洞一般都是中危以上,并且测试方式简单,所以这次打算研究一下。刚好在昨天碰到了一个涉及到敏感信息的接口,所以在此记录一下。
简单来说就是服务器配置了http头部字段,让客户端有资格跨域访问资源,而判断客户端权限的依据就是CORS。
我们通过浏览器向服务器发起请求,浏览器为其添加Origin表头(也就是该请求的来源URI),服务器接收到请求后会根据配置信息生成响应包,如果该请求来源是正确的,则允许跨域访问资源。
比如我们设置Origin为 http://foo.example.org
如果在响应头中出现了
Access-Control-Allow-Origin: http://foo.example.org
Access-Control-Allow-Credentials: true
则代表允许跨域访问资源。
所以我们在测试时可以修改Origin的值,通过响应头的内容来判断是否存在CORS漏洞。
不过大部分的厂商应该都是设置为匹配子域名可访问,所以如果我们能找到子域名下的一个XSS(反射型也可以)就能通过XSS+CORS漏洞来形成组合拳。
JSONP是一种协议,它是利用<script>
标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。服务端收到请求后,动态生成脚本产生数据,并在代码中以产生的数据为参数调用callback函数。
借用一张图(https://xz.aliyun.com/t/5143)
那么如果一个返回敏感信息的接口存在callback函数,我们可以通过外部写一个js函数,然后通过这个callback调用函数,弹出返回的敏感信息。比如
<script>function jsonp2(data){alert(JSON.stringify(data));}</script>
<script src="http://localhost/userinfo/?callback=jsonp2"></script>
在测试该漏洞的时候也很简单,只要fuzz一下callback参数,看看响应包内容即可。
在网站上到处点点,看看有没有返回敏感信息的接口,碰巧就发现了一个。
在这里有一个修改信息的操作,于是我开启抓包看了几个请求,发现一个很像访问接口的请求。
这里已经返回了用户的姓名,住址,电话,***等敏感信息。
看见了Origin我就想着试一试 CORS跨域漏洞,我先尝试修改 Origin为 http://foo.example.org
发现响应包中的Access-Control-Allow-Origin 没了,所以任意构造的Origin是不行的。
那么就尝试了一下它的子域名是否可以(废话
所以我现在的目标就是找一个xss,就是反射型的也可以利用了。
这个waf我绕了很久都绕不过去。。。然后我一点返回,却发现弹窗了
我审查一下元素发现
这居然是个dom型xss。。用户搜索了以后会将查询的内容放到搜索历史中,我总不可能让用户带着我的payload去搜索内容吧,但是CSRF可以啊。
我尝试将搜索的请求报文拿出来生成一个CSRF的POC,看看我自己访问以后搜索历史会不会出现我们的xsspayload。直接利用burp生成。
在生成的html页面直接点击后还是跳转到了waf界面。检查搜索历史,发现并没有payload。
然后我再次思考,既然要用户点击,那是不是可以做一个点击劫持呢?而这个站也刚好有着点击劫持的漏洞。所以我调整了一下按钮的位置最后生成了一个可以利用的页面。
那么问题再次出现,我要怎么把payload输进去,不可能让用户自己敲吧,所以我在想能不能用JavaScript给输入框提前赋默认值。先获取iframe的作为父页面,然后再获取父页面的输入框dom元素进行修改。
<script>
var childHtml = document.getElementById("frame").contentWindow;
var a = childHtml.document.getElementById("city").value='abc';
</script>
但是在chrome的console界面我看到无法获取到value的值,那么问题就应该是无法加载iframe中id为city的dom元素,应该是同源策略的问题。
后来我又想了想能否利用JavaScript来复制我们的payload,但是越往后想,我感觉攻击的思路越过于理想化,遂放弃了这个domxss的攻击方法。
重新回到返回敏感信息的接口,我想起了jsonp劫持,我好像还没有fuzz callback函数,结果是并不用fuzz,函数名就是callback。所以直接在本地构造好
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
</head>
<body>
<script>function jsonp2(data){alert(JSON.stringify(data));}</script>
<script src="http://user.xxxx.com/webapi/contact/query/?callback=jsonp2"></script>
</body>
</html>
但是我又翻了看看,这个弹窗里为什么只有姓名手机号和生日,最重要的地址和***号没了呢?回过头去看了一下访问接口的请求包
发现要显示的信息是有参数设置的,而我们又知道jsonp是不能发起post请求的,那么就不能带上这几个参数,但是我又仔细想了想,如果后端设置的并不只是post接收,还可以get接收呢?
修改了一下jsonp劫持的代码。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
</head>
<body>
<script>function jsonp2(data){alert(JSON.stringify(data));}</script>
<script src="http://user.xxxx.com/webapi/contact/query/?callback=jsonp2&attribute=base%2Caddress%2Cinvoice%2Ctravelcard%2Ccredentials&credentialsTypes=1%2C2%2C3%2**%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16"></script>
</body>
</html>
再次访问
这一次,***和地址都出现了。
这次虽然没有按照预想打出鸡肋漏洞的组合拳,但是也在思考中收获了很多,最终的jsonp劫持没有什么技术含量,师傅们见笑了。
参考文章:
https://gh0st.cn/archives/2017-12-20/1
https://xz.aliyun.com/t/6539
本文作者:星盟安全团队
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/127974.html