JSONP注入解析首席安全官频道

前言

JSONP注入是一个不太常见但影响非常广泛且极危险的漏洞,首席安全官频道由于最近几年对JSON, web APIs以及跨域通信的需求增多,不得不引起我们的重视。

什么是JSONP

这里我们假设大家都了解JSON为何物,以此为基础我们来谈谈JSONP。JSONP全名为JSON with Padding,其存在的意义便有绕过诸如同源策略强制执行XMLHttpRequest(AJAX requests)。

举个例子,我们的网上银行应用verysecurebank.ro,实现了以API调用返回当前用户的交易记录。

向verysecurebank.ro/getAccountTransactions发起一个HTTP请求,在客户端上以JSON格式进行呈现:

若我们另一个用于展示交易记录报告的站点reports.verysecurebank.ro需要获取详细的交易记录,由于受同源策略的限制(主机不同),AJAX是无法调用该页面的。

为了解决该问题,于是JSONP就登上了历史舞台。由于跨域脚本包含(Cross-domain script inclusion,主要用于外部加载jQuery, AngularJS等JavaScript库)是被允许的,但我们并不推荐这么做,聪明一点的做法便是:预先以一个回调(callback)进行响应。
注意:值得一提的是,当其中包含一个跨域脚本时,它会在包含应用的环境下运行,而非源环境下运行。
向API响应增加一个回调函数,封装JSON格式数据,允许我们在script标签中加载该API响应并且能够通过我们自己定义的回调函数获取其内容进行处理。

利用

以下为较为场景的利用场景:

    在响应中回调函数被硬编码

      基础函数调用

      对象方法调用

    动态调用回调函数

      URL完全可控(GET变量)

      URL部分可控(GET变量),但是附加有一个数字

      URL可控,但最初不会显示在请求之中

基础函数调用

如下即为一个常见的例子,myCallback回调函数被硬编码进响应中,混杂在JSON格式数据中:

我们首先可以通过定义myCallback函数,之后在script标签中引用该API来进行简单的利用:

 

 

注意:确保在包括响应之前已定义好该函数,否则就成了调用未定义的函数,无法抓取数据。当已经成功登录的用户访问恶意页面,我们就能抓取到他的数据。为了更简洁,在当前页面中我们仅显示了部分数据。

对象方法调用

此例与第一个例子几乎相同,另外可能会在ASP或是ASP.NET Web应用中遇到。在我们这个例子中,System.TransactionData.Fetch是作为一个回调函数放进JSON格式数据中的:

接下来我们为System对象中的TransactionData子对象简单创建一个Fetch方法:

由于最终结果都一样,所以从现在起也就不浪费篇幅继续粘贴截图了。

URL完全可控(GET变量)

这可能会是你会遇到的最常见的场景。回调函数出现在URL地址中,且我们能够进行完全控制。URL参数callback是允许进行修改的,于是我们将其修改为testing,然后看看返回的数据:

 

 

我们依旧可以使用之前的利用代码。只是如果响应中包含了script标签时,别忘了增加callback参数。

URL部分可控(GET变量),但是附加有数字

在该场景中,回调函数名后面附件了一些其他字符(通常为数字)。大部分情况下,我们想获取类似jQuery后面加上类似12345的短数字,最终回调函数为jQuery12345

从逻辑上来讲,仍然可以使用与上面相同的利用代码。我们仅仅只是在回调函数名后面填上12345,而非在脚本中添加。

如果这个数字没有进行硬编码,如果这个数字是动态变化的且对每一个会话都不一样。如果只是一个相较而言较小的数字,我们能以编程的方式为每一种可能性预定义函数。现在我们假设追加的数字最大为99999,我们可以用编程的方式包含每种可能,忽略掉后面的具体数字。以下样例代码,以一个简单的回调函数来进行说明:

这里发生了什么:我们对回调函数名jQuery进行硬编码,对函数后面追加的数字进行了一个限制。第一个循环用于在callbackNames数组中生成回调函数名,接着遍历数组并将返回的每个回调函数名加入global函数。请注意,为了缩短代码我仅仅只是弹出了交易中的金额:

在我的机器上,仅仅只是花了大约5秒钟就弹出了jQuery12345。这也意味着在Chrome浏览器下5秒钟大约是能够创建超过10000条函数的,所以我可以方向大胆的说,这个利用是可行的。

URL可控,但最初不会显示在请求之中

最后一个场景涉及到API,表面上与回调函数无关,所以没有可见的JSONP。这通常发生在开发者留下的“隐藏”的用于软件向后兼容的情况,或是在重构时根本就没有删除代码。所以当看到一个没有回调函数的API,我们可以直接手动向请求中添加回调试试。
如果有以下API调用verysecurebank.ro/getAccountTransactions,我们可以尝试去猜猜回调函数的变量:

    verysecurebank.ro/getAccountTransactions?callback=test

    verysecurebank.ro/getAccountTransactions?cb=test

    verysecurebank.ro/getAccountTransactions?jsonp=test

    verysecurebank.ro/getAccountTransactions?jsonpcallback=test

    verysecurebank.ro/getAccountTransactions?jcb=test

    verysecurebank.ro/getAccountTransactions?call=test

虽然这些都是很常见的回调函数名,你也不妨大胆去猜猜其他的。如果我们的回调请求成功得到响应,接下来就能抓取数据了。

基础数据抓取

直到现在我们都还只是显示数据,接下来我们就将数据抓取回我们本地。以下为一个简单的JSONP数据抓取:

在data参数中,向网页(交易数据)发起一个GET请求。
注意:因为data是一个对象,所以请确保有使用JSON.stringify(),并且我们也不希望最终得到的仅是一个[object 对象]。
注意:如果响应太长,由于HTTP GET长度限制你是无法获取完整数据的。这时你就得将其切换为POST方式。
以下为grabData.php代码,将接收到的数据写入data.txt文件。

常见问题

在玩耍JSONP漏洞时,可能会遇到这样那样的问题。这里我们也分享一些较为常见的一些问题解决方法。

Content-Type 与 X-Content-Type-Options

如果在响应中API请求头X-Content-Type-Options被设置为nosniff,Content-Type必须设置为JavaScript(text/javascript, application/javascript, text/ecmascript等.)才能在所有浏览器中运行。这是由于在响应中包含回调产生的问题,这时候响应不在解析JSON而是解析JavaScript。如果你想了解你使用的浏览器会将哪种Content-Types解析为JavaScript,进入https://mathiasbynens.be/demo/javascript-mime-type 网页看看吧。

在本例中,Content-Type被设置为application/json 和 X-Content-Type-Options: nosniff

 

 

在Google Chrome,Microsoft Edge以及Internet Explorer 11的最新版本,是能够成功阻止脚本运行的。然而Firefox 50.1.0(当前最新版本)就不能。
注意:如果没有设置X-Content-Type-Options: nosniff头,它能在所有的浏览器下工作。注意:由于X-Content-Type-Options是最近才引进的,旧版本的浏览器对于MIME类型检查不是很严格。看看Mozilla发布的浏览器兼容性:

响应代码

有时候我们会得到与200不同响应代码,特别是由于我们弄乱了响应。我在如下浏览器进行了测试:

    Microsoft Edge 38.14393.0.0

    Internet Explorer 11.0.38

    Google Chrome 55.0.2883.87

    Mozilla Firefox 50.1.0

的确还有一些发现:

响应码 浏览器
100 Continue Internet Explorer, Microsoft Edge, Google Chrome
101 Switching Protocols Google Chrome
301 Moved Permanently Google Chrome
302 Found Google Chrome
304 Not Modified Microsoft Edge

即使我们没有得到HTTP200,在其他的浏览器该漏洞也是可能利用的。

Referer检测绕过

1.使用data URI方案
如果这里有一个Referer检测,为了绕过检测我们可以选择不发送。怎么做呢?这就要引入Data URI了。
为了构造一个不带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下不工作

我们可以看到在API请求中没有发送HTTP Referer

2.从HTTPS向HTTP发起请求
如果目标网站可以通过HTTP访问,也可以通过将我们的代码托管在一个HTTPS页面来避免发送HTTP Referer。如果我们从HTTPS页面发起一个HTTP请求,浏览器为了防止信息泄漏是不会发送Referer header。以上我们要将恶意代码托管在一个启用了HTTPS的站点。
注意:由于mixed-content安全机制,在浏览器默认设置下是不会工作的。需要受害者手动允许浏览器发出的安全警告。

然而,在旧版本的浏览器中能正常工作,并且也不会发送HTTP Referer header,如下图:

解决方案

最后来看看如何防止此类情况,最直接最有效的方法便是CORS(Cross-Origin Resource Sharing)

    完全移除JSONP函数

    向API响应添加Access-Control-Allow-Origin header

    使用跨域AJAX请求

所以可以在reports.verysecurebank.ro中嵌入以下代码向verysecurebank.ro/getAccountTransactions发起跨域AJAX请求

API响应包括了Access-Control-Allow-Origin: reports.verysecurebank.ro:

接着我们获得verysecurebank.ro/getAccountTransactions中的内容:

总结

尽管JSONP的使用在减少,但仍有大量的站点在使用或是支持。最后一个提示,在玩耍JSONP的同时也别忘了检测映射文件下载,以及映射跨站脚本漏洞。
Happy pwning!

*参考来源:securitycafe,FB小编鸢尾编译,转载请注明来自FreeBuf(FreeBuf.COM)

2017-02-17 07:58 阅读:156