实用JSONP注入
这篇文章是翻译文章,看着比较基础,主要是入门了解,手翻加机翻,如有问题,那也只能凑合着看了,或是看原文: Practical JSONP Injection
JSONP注入是一个鲜为人知但相当普遍且危险的漏洞,由于JSON的采用率很高,Web API以及对跨域通信的迫切需求,它在最近几年浮出水面。
什么是JSONP?
假设每个人都知道JSON是什么,那么让我们来谈谈JSONP。 JSONP是填充的JSON,它的创建是为了绕过常见限制,例如对XMLHttpRequest(AJAX请求)强制实施的同源策略。(注:JSONP可以看文章: JSONP 教程)
举例说明,比如在线银行应用 http://verysecurebank.ro, 有个实现返回当前用户交易信息的API接口。
发送到http://verysecurebank.ro/getAccountTransactions的HTTP请求向我们展示了JSON格式的交易:(下图)
如果我们的报告应用程序(可从http://reports.verysecurebank.ro访问)希望获取交易详细信息,则由于同源策略(不同的目标主机),则无法使用AJAX的方式调用该请求。(下图:)
为了解决这个问题,JSONP开始发挥作用。由于允许跨域脚本包含(通常用于外部加载jQuery,AngularJS等JavaScript库),但不建议这样做,因此,一个巧妙的技巧显然解决了整个问题:在响应之前加上回调。
注意:即使很明显,也值得一提的是,当跨域包含脚本时,脚本是运行在当前的页面中,而不是请求的页面中。(注:如果不明白这个点,可以看上面给的JSONP教程中例子)
向返回JSON数据格式的API中增加一个回调处理,可以使我们在脚本标签之间加载API响应,并通过定义自己的回调函数来处理其内容。
利用
以下是你能碰到的从常见情况:
-
回调函数是在响应数据中硬编码
- 基本函数调用
- 对象方法调用
-
回调函数是动态设定的
- 可以通过URL(GET方法参数)进行完全控制
- 可从通过URL(GET方法参数)部分控制,但需要附加一个数字
- 可通过URL(GET方法参数)控制,但最初未显示在请求中
基本函数调用
一个非常常见的示例,其中myCallback回调在响应中进行了硬编码,并以JSON格式的数据包装:
我们可以通过先定义myCallback函数,然后在脚本标签内引用API调用来轻松利用此功能:
注意:请确保在包含响应之前定义函数,否则将调用未定义的函数,并且不会获取任何数据。 当登录的受害者访问我们的恶意页面时,我们将获取他的数据。为了简洁起见,我们在当前页面中以整齐的格式显示数据。
对象方法调用
我们只需为TransactionData对象创建Fetch方法,该对象已经是System对象的一部分。
注:System为自定义的东西,这个不需要关注这个问题
上面的结果类似的,这里就不截图了。
可以通过URL(GET方法参数)进行完全控制
这是您会遇到的最常见的情况。回调函数在URL中指定,我们可以完全控制它。 callback URL参数允许我们更改回调的名称,因此我们将其设置为testing并在响应中看到它已更改:
我们基本上可以使用相同的利用代码,只是在包含带有脚本标签的响应时不要忘记添加回调参数。
可从通过URL(GET方法参数)部分控制,但需要附加一个数字
在这种情况下,回调函数名称后面会附加一些东西,通常是一个数字。在大多数情况下,我们会得到类似jQuery的内容,并在其后附加一个短数字,例如12345,回调将变成jQuery12345。
逻辑上,漏洞利用代码保持不变,我们只需要在回调函数名称中添加12345,而在添加脚本时则无需添加。
但是,如果数字不是硬编码的怎么办?如果每次会话都是动态的且不同的怎么办?如果数量相对较少,我们可以通过编程方式为每种可能性预定义功能。假设附加数字最多为99999。我们可以以编程方式创建所有这些函数,因此无论附加什么数字,我们都已经有一个回调函数。这是示例代码,我使用了一个更简单的回调来说明结果:
这里发生了什么:我们硬编码的回调名称jQuery,我们为函数数量设置了限制。在第一个循环中,我们生成callbackName回调函数名称数组。然后,我们遍历数组并将每个回调名称转换为全局函数。请注意,为了缩短代码,我仅提醒第一笔交易中发送的金额。让我们看看它是如何工作的:
在我的机器上,花了大约5秒钟来显示警报,回调名称为jQuery12345。这意味着Chrome在不到5秒的时间内创建了10000个函数,因此,我要大胆地说这是一个可行的利用方法。
可通过URL(GET方法参数)控制,但最初未显示在请求中
最后一种情况涉及一个没有回调的API调用,因此没有可见的JSONP。当开发人员与其他软件保持“隐藏”的向后兼容性,或者重构时根本没有删除代码时,可能会发生这种情况。因此,当看到没有回调的API调用时,尤其是如果JSON格式的数据已经在括号之间时,请手动将回调添加到请求中。
如果我们具有以下API调用http://verysecurebank.ro/getAccountTransactions,我们可能会尝试猜测回调变量:
- http://verysecurebank.ro/getAccountTransactions?callback=test
- http://verysecurebank.ro/getAccountTransactions?cb=test
- http://verysecurebank.ro/getAccountTransactions?jsonp=test
- http://verysecurebank.ro/getAccountTransactions?jsonpcallback=test
- http://verysecurebank.ro/getAccountTransactions?jcb=test
- http://verysecurebank.ro/getAccountTransactions?call=test
这里列举了一些常用的回调名称,你可以随意地猜其他回调名称。如果请求返回中出现我们的回调内容,那说明已经成功了,可以获取一些重要的数据了。
基本数据抓取
由于我们到目前为止仅显示数据,因此让我们看看如何将其发送回给我们。这是获取JSONP数据的最小示例,可以用作概念证明。
我们使用data参数中的应用程序响应(事务)向数据采集器发出GET请求。
注意:请确保您在数据上使用JSON.stringify(),因为它是对象,并且我们不想在文件中只包含[object Object]。
注意:如果响应数据很大,请确保切换到POST,因为HTTP GET大小限制可能无法接收到完整的数据。
这是我们的grabData.php代码,我们将接收到的数据添加到data.txt文件中:
常见问题
在寻找JSONP漏洞的Web应用程序时,我们可能会遇到一些问题。在这里,我们尝试解决它们。
Content-Type and X-Content-Type-Options
如果在API请求的响应标头中,X-Content-Type-Options设置为nosniff,则Content-Type必须设置为JavaScript(文本/ javascript,应用程序/ javascript,文本/ ecmascript等)才能正常工作所有浏览器。发生这种情况是因为通过在响应中包含回调,响应不再是JSON,而是JavaScript。
(注:X-Content-Type-Options)
如果您想知道浏览器将哪种内容类型解释为JavaScript,请将您的浏览器指向https://mathiasbynens.be/demo/javascript-mime-type
最新版本的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
发现这些不一致之处:
Response Code | Works in |
---|---|
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 |
因此,即使我们没有获得200 HTTP代码,该漏洞仍可在其他浏览器中利用。
Referrer头检查绕过
- 使用数据URI方案(Data URI scheme)
如果有HTTP Referer检查,我们可以尝试不发送它以绕过验证。我们该怎么做?数据URI简介。 我们可以滥用数据URI方案,以便在没有HTTP Referer的情况下发出请求。由于我们要处理的代码包括引号,双引号和其他语法破坏字符,因此我们将对base64编码有效负载(回调定义和脚本包含)。
使用语法:data:text/plain;base64,our_base64_encoded_code
以下是三个主要的HTML标记,这些标记允许我们使用数据URI方案:
- iframe (in the src attribute) – Internet Explorer 浏览器无效
- embed(in the src attribute) – Internet Explorer 和 Microsoft Edge 浏览器无效
- object(in the data attribute) – Internet Explorer 和 Microsoft edge 浏览器无效
我们可以看到没有Http Referrer头的请求发送到API。
referer-browser.png
- 从Https到Http的请求
如果可以通过HTTP访问目标网站,我们还可以通过将代码托管在HTTPS页面上来避免发送HTTP引用。如果我们从HTTPS页面发出HTTP请求,则指示浏览器不要发送Referer标头,以防止信息泄漏。 我们要做的就是将恶意代码托管在启用HTTPS的网站上。(注:在https页面给http发送请求不发送Referrer头的问题是老版本浏览器问题)
注意:由于采用了混合内容安全机制,因此在具有默认设置的现代Web浏览器中无法使用此功能。受害者需要手动接受浏览器的安全警告。
上面只能运行在旧版本的浏览器中,没有Http Referrer头的请求被发送,我们能看到:
怎么修复这个问题?(JSONP注入)
最后,我们看看如何防止这种情况发生呢,最现代的方式是CORS(跨站资源共享)
1. 完全移除JSONP功能
2. 添加 Access-Control-Allow-Origin 头信息在你的API响应中
3. 使用跨域AJAX请求
在页面http://reports.verysecurebank.ro 中嵌入发送到http://verysecurebank.ro/getAccountTransactions跨域AJAX请求:
这个API响应中包含 Access-Control-Allow-Origin: http://reports.verysecurebank.ro 头信息:
我们能得到从 http://verysecurebank.ro/getAccountTransactions返回的内容:
结论
尽管JSONP的使用率在下降,但是仍有大量网站仍在使用或支持JSONP。最后一点,在处理JSONP时,请不要忘记检查Reflected File Download和Reflected Cross-Site Scripting漏洞。