免责声明
道一安全(本公众号)的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文所提供的工具仅用于学习,禁止用于其他!!!
由浅入深理解JSONP并拓展
简单描述
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的src 不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
动态创建<script>标签,设置其src,回调函数在src中设置:
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
在页面中,返回的JSON作为response参数传入回调函数中,我们通过回调函数来来操作数据。
function handleResponse(response){
// 对response数据进行操作代码
}
上面是简单直接的对JSONP 的描述,可能有些人不是很懂,我们下面一步一步分析
层层深入
先通过一个简单的实例简单的理解一下同源策略的作用
首先我们在本地写入
1.html
<html>
<head>
<title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
function jsonhandle(data){
alert("age:" + data.age + "name:" + data.name);
}
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
type : "get",
url : "http://check.k0rz3n.com/test.php?id=1",
dataType: "jsonp",//指定我们的请求是一个 jsonp 的请求
success : function(data) {//success 指定的是默认的回调函数
jsonhandle(data);
}
});
});
</script>
</body>
</html>
我服务器上的test.php会返回json 格式的数据给客户端
test1.php
header('Content-Type:application/json; charset=utf-8');
$data = array('age'=>19,'name'=>'jianshu');
exit(json_encode($data));
如果正常访问的话,那么我们的浏览器应该会弹出对话框,结果我们会得到这样的结果
可以看到,浏览器发现这是一个跨域的请求,但是他在服务器的返回头中缺没有发现
Access-Control-Allow-Origin 值允许 http://localhost 的访问,于是就拦截了。
也就是说,虽然浏览器受到了同源策略的限制,不允许实现跨域访问,但是由于在开发过程中前后端的交互过程中不可避免地会涉及到跨域的请求(设计同源策略的人想必也发现了这个问题),于是设计者给我们留了一个后门,就是只要服务器响应头中返回允许这个源的选项,那么跨域请求就会成功。(这里纠正一个误区,不要认为浏览器默认支持同源策略就意味着不同源的请求就不能发出去,其实还是能发出去的,只是要看响应头而已。)
我们知道在页面中有几个东西是对同源策略免疫的,那就是 <img> 的src 、<link> 的 href 还有就是<script>的 src , JSONP 就是利用 script 标签的sec 属性实现跨区域请求的
script标签的请求不论是不是同源一律不受同源策略的限制,那我们就找到了解决跨域访问的方法(似乎这个方法一开始就存在…..)
我们改变一下代码,本地直接通过script标签请求服务器上的js,js 的内容就是调用参数已经传进去的本地的js函数
2.html
<html>
<head>
<title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
function jsonhandle(data){
alert("age:" + data.age + "name:" + data.name);
}
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript" src="http://check.k0rz3n.com/remote.js"></script>
</body>
</html>
remote.js
jsonhandle({
"age" : 15,
"name": "John",
})
注意:
(1)远程的js 代码不需要script标签
(2)这其实也给了我们一些启示,就是我们使用 callback 函数请求的页面实际上类型是javascript 的类型,我们可以在这里看一下浏览器会将哪些类型当做 javascript 解析:https://mathiasbynens.be/demo/javascript-mime-type
下图可以看到我们成功利用<script>实现了跨域的访问。
那JSONP和这个有啥关系,感觉已经实现跨域了还没有提到一点JSONP,上面说JSONP是基于script标签的,个人感觉JSONP的优势就是能够实现呢比较方便的函数选择,传一个参数就行了,不用像直接调用那样必须要换js文件。
真相浮现
先用下面的代码模拟jsonp的调用过程方便大家更好的理解jsonp的运行过程
3.html
<html>
<head>
<title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
function jsonhandle(data){
alert("age:" + data.age + "name:" + data.name);
}
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript">
$(document).ready(function(){
var url = "http://check.k0rz3n.com/test1.php?id=1&callback=jsonhandle";
var obj = $('<script></script>');
obj.attr("src",url);
$("body").append(obj);
});
</script>
</body>
</html>
test1.php
$data = array(
'age' => 20,
'name' => 'dada',
);
$callback = $_GET['callback'];
echo $callback."(".json_encode($data).")";
return;
我们在scipt标签里面给出的链接是我远程服务器的一个php的代码,我给这个文件传递了一个参数,作为我要调用的函数。服务器接收到这个参数以后把它当做函数名,并给这个函数传递了一个json的值作为用户调用的函数的参数,最终实现调用
下面是调用成功的截图
实际上,jquery 给我们提供了现成的接口,我们可以不用这么麻烦
4.html
<html>
<head>
<title>GoJSONP</title>
</head>
<body>
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript">
function jsonhandle(data){
alert("age:" + data.age + "name:" + data.name);
}
</script>
<script type="text/javascript">
$(document).ready(function(){
$.ajax({
type : "get",
url : "http://check.k0rz3n.com/test1.php?id=1",
dataType: "jsonp",
jsonp:"theFunction", //指定回调函数在 URL 中的参数名(不指定默认为 callback)
jsonpCallback: "jsonhandle",//指定回调函数名称(如果不指定,服务器会随机分配一个jQueryxxx 的名字)
success : function(data) {
console.info("调用success");
}
});
});
</script>
</body>
</html>
这时候的请求的 URL 就像下面这个样子:
http://check.k0rz3n.com/test1.php?id=1&theFunction=jsonhandle
服务器端页面为:
test2.php
$data = array(
'age' => 20,
'name' => 'dada',
);
$callback = $_GET['theFunction'];
echo $callback."(".json_encode($data).")";
return;
怎么样,大概理解了吧,其实可以用一个非常形象的例子说明:
幼稚园吃午饭,小明吧贴有自己名字的碗(回调函数)给了幼稚园阿姨(服务器),阿姨给小明盛好饭(json参数)以后又把碗还给了小明。
就是这样的一个过程。
相关拓展:JSONP攻击
1.JSONP 跨域劫持
实际上就是由于服务器端对JSONP 的请求来源的检查不严格导致的
攻击者模拟用户向有漏洞的服务器发送JSONP请求,然后就获取到了用户的某些信息,再将这些信息发送到攻击者可控的服务器
2.JSONP 跨域劫持token 实现CSRF
通过 jsonp 发起请求,得到泄露的 csrf_token 然后,利用这个token 实现CSRF 攻击
3.Referer 头的绕过
在攻击过程中可能会涉及到 referer 头的绕过
-
data:URL
为了逃避他的检测我们可以选择不发送referer这个头,那么怎么做呢?这就涉及到 data:URL 头
为了构造一个不带HTTP Referer的请求,我们可以滥用data URI方案。因为我们正在处理的代码包含了引号,双引号,以及其他一些被阻止的语句,接着使用base64编码我们的payload(回调函数定义以及脚本包含)
data:text/plain;base64our_base64_encoded_code:
以下3个HTML标签允许我们使用data URI方案:
iframe (在src属性中) – Internet Explorer下不工作
embed (在src属性中) – Internet Explorer及Microsoft Edge下不工作
object (在data属性中) – Internet Explorer及Microsoft Edge下不工作
2.从HTTPS向HTTP发起请求
如果目标网站可以通过HTTP访问,也可以通过将我们的代码托管在一个HTTPS页面来避免发送HTTP Referer。如果我们从HTTPS页面发起一个HTTP请求,浏览器为了防止信息泄漏是不会发送Referer header。以上我们要将恶意代码托管在一个启用了HTTPS的站点。
注意:由于mixed-content安全机制,在浏览器默认设置下是不会工作的。需要受害者手动允许浏览器发出的安全警告。
JSONP 劫持原理与挖掘方法
0X01 什么是 JSONP 劫持
由于之前的那篇文章已经详细介绍过 jsonp 的工作原理,所以这里就不再详细介绍原理了,就简单的说一下:
JSONP 就是为了跨域获取资源而产生的一种非官方的技术手段(官方的有 CORS 和 postMessage),它利用的是 script 标签的 src 属性不受同源策略影响的特性,
那么劫持又是怎么回事呢?其实我们在学安全的过程中对劫持这个词可以说是一点也不陌生,我们遇到过很多的劫持的攻击方法,比如:dns 劫持、点击劫持、cookie劫持等等,也正如劫持这个词的含义:“拦截挟持”,dns 劫持就是把 dns 的解析截获然后篡改,点击劫持就是截获你的鼠标的点击动作,在用户不知情的情况下点击攻击者指定的东西,cookie 劫持就是获取用户的 cookie,然后可以进一步伪造身份,那么同样, jsonp 劫持就是攻击者获取了本应该传给网站其他接口的数据
0X02 JSONP 漏洞的利用过程及危害
通过JSONP技术可以实现数据的跨域访问,必然会产生安全问题,如果网站B对网站A的JSONP请求没有进行安全检查直接返回数据,则网站B 便存在JSONP 漏洞,网站A 利用JSONP漏洞能够获取用户在网站B上的数据。
1.JSONP漏洞利用过程如下:
1)用户在网站B 注册并登录,网站B 包含了用户的id,name,email等信息;
2)用户通过浏览器向网站A发出URL请求;
3)网站A向用户返回响应页面,响应页面中注册了JavaScript的回调函数和向网站B请求的script标签,示例代码如下:
<script type="text/javascript">
function Callback(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://B.com/user?jsonp=Callback"></script>
4)用户收到响应,解析JS代码,将回调函数作为参数向网站B发出请求;
5)网站B接收到请求后,解析请求的URL,以JSON 格式生成请求需要的数据,将封装的包含用户信息的JSON数据作为回调函数的参数返回给浏览器,网站B返回的数据实例如下:
Callback({"id":1,"name":"test","email":"[email protected]"})。
6)网站B数据返回后,浏览器则自动执行Callback函数对步骤4返回的JSON格式数据进行处理,通过alert弹窗展示了用户在网站B的注册信息。另外也可将JSON数据回传到网站A的服务器,这样网站A利用网站B的JSONP漏洞便获取到了用户在网站B注册的信息。
2.JSONP 漏洞利用过程示意图
3.JSONP 劫持漏洞的危害
JSONP是一种敏感信息泄露的漏洞,经过攻击者巧妙而持久地利用,会对企业和用户造成巨大的危害。攻击者通过巧妙设计一个网站,网站中包含其他网站的JSONP漏洞利用代码,将链接通过邮件等形式推送给受害人,如果受害者点击了链接,则攻击者便可以获取受害者的个人的信息,如邮箱、姓名、手机等信息,这些信息可以被违法犯罪分子用作“精准诈骗”。对方掌握的个人信息越多,越容易取得受害人的信任,诈骗活动越容易成功,给受害人带来的财产损失以及社会危害也就越大。
J0X03 SOP 漏洞的挖掘思路
这里我采用chrome浏览器的调试窗口进行挖掘weibo.com中存在的漏洞(测试之前需要登录一下,因为我们需要检测是不是会有敏感信息泄露)
首先把Preserve log选项勾上,这样用来防止页面刷新跳转的时候访问记录被重置,也方便我们进行下一步的筛选。
然后 F5 刷新,进入 NetWork 标签 ,CTRL+F 查找一些关键词 如 callback json jsonp email
然后我们需要人工确认这个请求的返回值是否有泄露用户的敏感信息,并且能被不同的域的页面去请求获取,这里以上面查找到的 jsonp 为例
发现并不是什么很有价值的信息,再来看看能不能被不同的域的页面请求到(也就是测试一下服务器端有没有对其验证请求来源)
发现换成了别的浏览器还是能检测到,说明验证的来源有些问题
注意:
上面的测试只是我为了简单的演示整个流程,所以在测试前我并没有登录,因此,上面的测试并不能说明漏洞存在
当然,这种人工的低效的检测方式我们完全可以将其变成主动或者被动的扫描器实现,那样效率会高得多
自动化测试工具Selenium + Proxy + 验证脚本
(1)Selenium:可用于自动化对网页进行测试,“到处”点击按钮、超链接,以期待测试更多的接口;
(2)Proxy:用于代理所有的请求,过滤出所有包含敏感信息的JSONP请求,并记录下HTTP请求;
(3)验证脚本:使用上述的HTTP请求,剔除referer字段,再次发出请求,测试返回结果中,是否仍包敏感信息,如果有敏感信息,说明这个接口就是我们要找的!
0X04 JSONP 漏洞利用技巧
1.利用技巧
JSONP 漏洞主要被攻击者用来在受害者不知不觉中窃取他们的隐私数据,常常被一些 APT 组织采用进行信息收集和钓鱼的工作(水坑攻击),下面的一个例子就可以说是在模拟水坑攻击
当我们发现信息泄露的 jsonp 接口以后我们要做的就是在自己的网站上写一个脚本,然后引诱受害者去访问这个网站,一旦访问了这个网站,脚本就会自动运行,就会想这个接口请求用户的敏感数据,并传送到攻击者的服务器上
$.ajax({
url: 'https://api.weibo.com/2/{隐藏了哦}',
type: 'get',
dataType: 'jsonp',
}).done(function(json){
var id = json["data"]["id"];
var screen_name = json["data"]["screen_name"];
var profile_image_url = json["data"]["profile_image_url"];
var post_data = "";
post_data += "id=" + id + "&";
post_data += "screen_name=" + screen_name + "&";
post_data += "profile_image_url=" + encodeURIComponent(profile_image_url);
console.log(post_data);
// 发送到我的服务器上
}).fail(function() {});
这样就能收到大量用户的敏感信息了
上述相关代码被一个师傅放在了 github 上,
https://github.com/qiaofei32/jsonp_info_leak
2.相关扩展
(1)既然是窃取敏感信息,那么敏感信息除了一些 email 手机号 用户名等还有什么呢?没错,甚至可以是 CSRF Token 信息,有时候在 CSRF token 获取不到但是又找不到 XSS 的攻击点的时候不妨考虑一下 jsonp 劫持,看看会不会有惊喜
(2)还有一点,你有没有觉得这个攻击方式有点类似于 CSRF ,是的,的确很像,因此这也就引出了非常类似的修复方案。
0X05 防护方案
1、严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 等。
2、严格安装 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 )。
3、严格过滤 callback 函数名及 JSON 里数据的输出。
4、严格限制对 JSONP 输出 callback 函数名的长度(如防御上面 flash 输出的方法)。
5、其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,听过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用。
原文地址:
https://www.k0rz3n.com/2019/03/07/JSONP%20%E5%8A%AB%E6%8C%81%E5%8E%9F%E7%90%86%E4%B8%8E%E6%8C%96%E6%8E%98%E6%96%B9%E6%B3%95/
0X06 参考链接
http://www.mottoin.com/tech/123337.html
https://www.anquanke.com/post/id/97671
https://xiaix.me/fan-yi-wa-jue-tong-yuan-fang-fa-zhi-xing-lou-dong-same-origin-method-execution/
https://wooyun.js.org/drops/JS%E6%95%8F%E6%84%9F%E4%BF%A1%E6%81%AF%E6%B3%84%E9%9C%B2%EF%BC%9A%E4%B8%8D%E5%AE%B9%E5%BF%BD%E8%A7%86%E7%9A%84WEB%E6%BC%8F%E6%B4%9E.html
https://www.infosec-wiki.com/?p=455211
https://www.cnblogs.com/52php/p/5677775.html
http://www.91ri.org/13407.html
https://www.freebuf.com/articles/web/70025.html
原文始发于微信公众号(道一安全):JSONP原理和劫持原理与挖掘方法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论