JSONP注入是一个不太常见但影响非常广泛且极危险的漏洞,由于最近几年对JSON, web APIs以及跨域通信的需求增多,不得不引起我们的重视。
这里我们假设大家都了解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响应并且能够通过我们自己定义的回调函数获取其内容进行处理。
以下为较为常见的利用场景:
在响应中回调函数被硬编码
动态调用回调函数
如下即为一个常见的例子,myCallback回调函数被硬编码进响应中,混杂在JSON格式数据中:
我们首先可以通过定义myCallback函数,之后在script标签中引用该API来进行简单的利用:
注意:确保在包括响应之前已定义好该函数,否则就成了调用未定义的函数,无法抓取数据。当已经成功登录的用户访问恶意页面,我们就能抓取到他的数据。为了更简洁,在当前页面中我们仅显示了部分数据。
此例与第一个例子几乎相同,另外可能会在ASP或是ASP.NET Web应用中遇到。在我们这个例子中,System.TransactionData.Fetch是作为一个回调函数放进JSON格式数据中的:
接下来我们为System对象中的TransactionData子对象简单创建一个Fetch方法:
由于最终结果都一样,所以从现在起也就不浪费篇幅继续粘贴截图了。
这可能会是你会遇到的最常见的场景。回调函数出现在URL地址中,且我们能够进行完全控制。URL参数callback是允许进行修改的,于是我们将其修改为testing,然后看看返回的数据:
我们依旧可以使用之前的利用代码。只是如果响应中包含了script标签时,别忘了增加callback参数。
在该场景中,回调函数名后面附件了一些其他字符(通常为数字)。大部分情况下,我们想获取类似jQuery后面加上类似12345的短数字,最终回调函数为jQuery12345
从逻辑上来讲,仍然可以使用与上面相同的利用代码。我们仅仅只是在回调函数名后面填上12345,而非在脚本中添加。
如果这个数字没有进行硬编码,如果这个数字是动态变化的且对每一个会话都不一样。如果只是一个相较而言较小的数字,我们能以编程的方式为每一种可能性预定义函数。现在我们假设追加的数字最大为99999,我们可以用编程的方式包含每种可能,忽略掉后面的具体数字。以下样例代码,以一个简单的回调函数来进行说明:
这里发生了什么:我们对回调函数名jQuery进行硬编码,对函数后面追加的数字进行了一个限制。第一个循环用于在callbackNames数组中生成回调函数名,接着遍历数组并将返回的每个回调函数名加入global函数。请注意,为了缩短代码我仅仅只是弹出了交易中的金额:
在我的机器上,仅仅只是花了大约5秒钟就弹出了jQuery12345。这也意味着在Chrome浏览器下5秒钟大约是能够创建超过10000条函数的,所以我可以方向大胆的说,这个利用是可行的。
最后一个场景涉及到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漏洞时,可能会遇到这样那样的问题。这里我们也分享一些较为常见的一些问题解决方法。
如果在响应中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
的确还有一些发现:
即使我们没有得到HTTP200,在其他的浏览器该漏洞也是可能利用的。
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!
转自:https://www.freebuf.com/articles/web/126347.html