- 英文原文:JSON Hijacking
- 完整译文:JSON劫持漏洞(详细讲解利用JSON从而进行数据劫持的漏洞攻防策略)
- 原文解析:JSON劫持漏洞分析和攻防演练
声明(阅读前必读!!!):
本文为JSON Hijacking的英汉翻译对照。本人是一个英语渣,英语四级都没过。翻译此文存粹用于英语学习,而且大部分还是用翻译软件翻译的。请谨慎阅读此译文,注意不要被译文雷到。
A while back I wrote about a subtle JSON vulnerability which could result in the disclosure of sensitive information. That particular exploit involved overriding the JavaScript Array constructor to disclose the payload of a JSON array, something which most browsers do not support now.
前阵子我写了篇关于一个微妙的JSON的漏洞(Anatomy of a Subtle JSON Vulnerability)可能导致敏感信息泄露的文章。这种特别的漏洞攻击(exploit)主要通过覆盖JavaScript数组的构造函数,从而劫持暴露的JSON数组数据,现在大多数的浏览器都不支持防御这些漏洞攻击。
However, there’s another related exploit that seems to affect many more browsers.It was brought to my attention recently by someone at Microsoft and Scott Hanselman and I demonstrated it at the Norwegian Developers Conference last week, though it has been demonstrated against Twitter in the past.
然而,还有另外一个与exploit相关的漏洞有可能会对更多的浏览器造成影响。最近在和微软的ScottHanselman交流时引起了我对这个问题的注意,并且我在上周的挪威开发者大会上演示了这个漏洞,虽然这个漏洞曾经出现在Twitter(推特)上。
Before I go further, let me give you the punch line first in terms of what this vulnerability affects.
This vulnerability requires that you are exposing a JSON service which…
- returns sensitive data.
- returns a JSON array.
- responds to GET requests.
- the browser making the request has JavaScript enabled (very likely the case)
- the browser making the request supports the __defineSetter__ method.
在我深入讨论前,让我首先给你讲解下形成JSON劫持漏洞的条件。
这个JSON劫持漏洞需要你暴露出JSON服务(指JSON服务地址被攻击者知道)并且进行如下操作:
- 返回敏感的数据
- 返回一个JSON数组
- 响应一个GET请求
- 发送请求的浏览器启用了JavaScript(很有可能)
- 发送请求的浏览器支持__defineSetter__方法
Thus if you never send sensitive data in JSON format, or you only send JSON in response to a POST request, etc. then your site is probably not vulnerable to this particular vulnerability (though there could be others).
因此,如果你从不发送敏感的JSON格式数据,或者你仅仅发送POST请求以响应获取JSON等等操作,那么你的网站可能不会受到这个特定的漏洞的攻击(虽然有可能是其他人)。
I’m terrible with Visio, but I thought I’d give it my best shot and try to diagram the attack the best I could. In this first screenshot, we see the unwitting victim logging into the vulnerable site, and the vulnerable site issues an authentication cookie, which the browser holds onto.
我讨厌使用Visio(Microsoft Office Visio:绘制流程图的软件),但我想我会尽我所能的通过图表来更好的讲解这个过程。在第一个屏幕截图上,我们看到不知情的受害者登陆了这个容易受到攻击的网站,而这个易受攻击的网站向浏览器发送一个身份验证的cookie并且浏览器将它保存起来(浏览器会将这个HTTP的身份认证信息和相关的cookie缓存起来)。
At some point, either in the past, or the near future, the bad guy spams the victim with an email promising a hilariously funny video of a hamster on a piano.
在某些时候,无论是在过去,或者是在将来,总有一些坏人(恶意攻击者)不停的发送着大量的垃圾邮件(这里应该是钓鱼/欺诈邮件),受害者会收到一封内容是推荐一个非常有趣的视频的电子邮件,比如邮件中带有"钢琴上的松鼠"这样内容的一个视频链接。
But the link actually points to the bad guy’s website. When the victim clicks on the link, the next two steps happen in quick succession. First, the victim’s browser makes a request for the bad guy’s website.
但是这个链接实际上是指向这些恶意攻击者的网站(就是钓鱼网站),当受害人点击了这个链接,接下来会连续执行两个步骤。首先,受害人的浏览器会向恶意攻击者的网站发送一个GET请求。
The website responds with some HTML containing some JavaScript along with a script tag. When the browser sees the script tag, it makes another GET request back to the vulnerable site to load the script, sending the auth cookie along.
这些恶意攻击者的网站(钓鱼网站)会响应并返回一些HTML语句,这些HTML语句包含着带有script标签的JavaScript语句(恶意脚本语句)。当浏览器运行到这些script标签时,它就会执行这段代码并向那些容易受到攻击的网站(这里假设攻击和劫持的网站是http://vulnerable.example.com/Json)发送另一个加载脚本的GET请求(如上图所示,这里会将从网站获取的响应文本放到/替换到script标签中,一般是返回JSON数据),该请求还会将受害人浏览器中的身份验证cookie一同发送过去。
The bad guy has tricked the victim’s browser to issue a request for the JSON containing sensitive information using the browser’s credentials (aka the auth cookie). This loads the JSON array as executable JavaScript and now the bad guy has access to this data.
攻击者通过伪装成受害者的浏览器,利用浏览器的授权(就是上面发送过去的身份验证cookie) 发出一个请求,这个请求可以到获取到拥有敏感数据的JSON数组。攻击者通过将请求的JSON数组载入到script中以替换重写成可执行的脚本语句,从而窃取到这些数据(脚本语句会将窃取到的数据发送给攻击者)。
To gain a deeper understanding, it may help to see actual code (which you can download and run) which demonstrates this attack.
Note that the following demonstration is not specific to ASP.NET or ASP.NET MVC in any way, I just happen to be using ASP.NET MVC to demonstrate it. Suppose the Vulnerable Website returns JSON with sensitive data via an action method like this.
为了能更深入的了解,看看这些演示攻击的实际代码,这样也许对我们理解有所帮助(你可以将代码下载拷贝下来并运行)。
另外要注意的是下面的演示无论如何都不是专门针对ASP.NET 或者 ASP.NET MVC,我只是碰巧使用ASP.NET MVC来演示它。假设这个容易受到攻击的网站通过一个Action方法返回敏感的JSON数据。
[Authorize]
public JsonResult AdminBalances() {
var balances = new[] {
new {Id = 1, Balance=3.14},
new {Id = 2, Balance=2.72},
new {Id = 3, Balance=1.62}
};
return Json(balances);
}
Assuming this is a method of HomeController, you can access this action via a GET request for /Home/AdminBalances which returns the following JSON:
Notice that I’m requiring authentication via the AuthorizeAttribute on this action method, so an anonymous GET request will not be able to view this sensitive data.
假如这是一个HomeController控制器的的方法,你可以通过向“/Home/AdminBalances”这个URL地址发送GET请求,从而获取Action方法返回的JSON数据:
[{"Id":1,"Balance":3.14},{"Id":2,"Balance":2.72},{"Id":3,"Balance":1.62}]
请注意,我还通过Action方法上声明了AuthorizeAttribute(Authorize属性)使得请求这个方法必须通过身份验证,所以一个匿名(指未通过身份验证)的GET请求将无法查看此敏感数据。
The fact that this is a JSON array is important. It turns out that a script that contains a JSON array is a valid JavaScript script and can thus be executed. A script that just contains a JSON object is not a valid JavaScript file. For example, if you had a JavaScript file that contained the following JSON:
事实上,这是一个JSON数组是很重要的。事实证明,包含着一个JSON数组的脚本是一个有效的JavaScript脚本。一个仅包含着JSON对象的脚本不是有一个有效的JavaScript文件。例如,如果您有一个包含以下JSON的JavaScript文件:
{"Id":1, "Balance":3.14}
And you had a script tag that referenced that file:
并且你有一个引用这个脚本文件的script标签:<script src=”http://example.com/SomeJson”></script>
You would get a JavaScript error in your HTML page. However, through an unfortunate coincidence, if you have a script tag that references a file only containing a JSON array, that would be considered valid JavaScript and the array gets executed.
你将会在HTML页面中得到一个JavaScript错误。然而,通过一个不幸的巧合,如果你有一个script标签,并且这个脚本标签引用文件的内容只有JSON数组,那么这(指的是script标签所引用文件中的JSON数组内容)将被认为是有效的JavaScript和数组被执行。
Now let’s look at the HTML page that the bad guy hosts on his/her own server:
现在让我们看看放置在这些攻击者服务器主机上的HTML页面:
<html>
...
<body>
<script type="text/javascript">
Object.prototype.__defineSetter__('Id', function(obj){alert(obj);});
</script>
<script src="http://example.com/Home/AdminBalances"></script>
</body>
</html>
What’s happening here? Well the bad guy is changing the prototype for Object using the special __defineSetter__ method which allows overriding what happens when a property setter is being called.
这里发生了什么?没错,恶意攻击者改变了对象的原型,正是利用了__defineSetter__这个特殊方法在原型对象调用属性设置方法的时候进行覆盖。
In this case, any time a property named Id is being set on any object, an anonymous function is called which displays the value of the property using the alert function. Note that the script could just as easily post the data back to the bad guy, thus disclosing sensitive data.
在这个案例中,无论何时所有的对象都会设置一个属性名称为Id的字段(通过__defineSetter__方法),一个通过使用alert函数显示原型属性值的匿名函数将被调用和触发。注意,该脚本(上面使用alert函数显示属性值的脚本)可以很容易地替换成将数据上传给攻击者的脚本,敏感数据就是因此被泄露的。
As mentioned before, the bad guy needs to get you to visit his malicious page shortly after logging into the vulnerable site while your session on that site is still valid. Typically a phishing attack via email containing a link to the evil site does the trick.
正如我前面所说的(要实现上面的动作),攻击者需要你访问他的恶意网页,前提你刚刚登陆这个受攻击的页面不久并且页面中的会话仍然保持有效。最常见的钓鱼攻击伎俩就是通过发送一个包含恶意网站链接的邮件(诱骗受害人点击链接)。
If by blind bad luck you’re still logged into the original site when you click through to the link, the browser will send your authentication cookie to the website when it loads the script referenced in the script tag. As far as the original site is concerned, you’re making a valid authenticated request for the JSON data and it responds with the data, which now gets executed in your browser. This may sound familiar as it is really a variant of a Cross Site Request Forgery (CSRF) attack which I wrote about before.
如果你运气不好没注意到这个钓鱼链接,当你点击了这个链接时你仍然保持原来网站的登陆状态,那么在进入钓鱼网站后载入script标签的引用时,浏览器将会发送你的身份验证cookie至原来的网站。直到与原来网站链接上为止,你会发送一个包含有效身份验证的请求从而通过响应取得JSON数据,这些数据会获取到你的浏览器中。这可能听起来很熟悉,因为它真的是一个变种的跨站请求伪造(CSRF)攻击,之前我有写过相关的文章。
If you want to see it for yourself, you can grab the CodeHaacks solution from GitHub and run the JsonHijackDemo project locally (right click on the project and select Set as StartUp Project. Just follow the instructions on the home page of the project to see the attack in action. It will tell you to visit http://demo.haacked.com/security/JsonAttack.html.
如果你想亲身感受一下,你可以从GitHub上获取这个CodeHaacks的解决方案,并在本地运行JsonHijackDemo项目(右键单击该项目,并选择设置为启动项目,只需按照项目主页上的说明进行攻击操作,它会告诉你要访问这个页面:http://demo.haacked.com/security/JsonAttack.html 。
Note that this attack does not work on IE 8 which will tell you that __defineSetter__ is not a valid method. Last I checked, it does work on Chrome and Firefox.
请注意,这种攻击方式对IE8浏览器是无法起作用的,它会提示你 __defineSetter__不是一个有效的方法。上一次我测试的时候,在Chrome和Firefox都可以正常的起到作用。
The mitigation is simple. Either never send JSON arrays OR always require an HTTP POST to get that data (except in the case of non-sensitive data in which case you probably don’t care). For example, with ASP.NET MVC, you could use the AcceptVerbsAttribute to enforce this like so:
避免措施是简单的。要么从不发送的JSON数组或者获取数据时必须使用一个HTTP POST请求(除非这些数据不是敏感的数据,在这种情况下你可能毫不/无需不关心这些) 。例如,在ASP.NET MVC中,你可以使用AcceptVerbsAttribute执行,像下面这样:
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
/*
*这就纠正下,现在新的ASP.NET MVC中,如果要限制操作方法只能接受POST请求,
*只要在操作方法上声明[HttpPost]这个特性就可以了
*原文中的[AcceptVerbs(HttpVerbs.Post)]已经被淘汰不用了。
*/
public JsonResult AdminBalances() {
var balances = new[] {
new {Id = 1, Balance=3.14},
new {Id = 2, Balance=2.72},
new {Id = 3, Balance=1.62}
};
return Json(balances);
}
One issue with this approach is that many JavaScript libraries such as jQuery request JSON using a GET request by default, not POST. For example, $.getJSON issues a GET request by default. So when calling into this JSON service, you need to make sure you issue a POST request with your client library.
这个方法存在着一个问题,许多像JQuery这样的JavaScript类库默认发送JSON请求都是使用GET,而不是POST。例如,$.getJSON 默认就是发起一个GET请求。因此调用这个JSON的服务时,你需要确保你的客户端库是发出一个POST请求。
ASP.NET and WCF JSON service endpoints actually wrap their JSON in an object with the “d” property as I wrote about a while back. While it might seem odd to have to go through this property to get access to your data, this awkwardness is eased by the fact that the generated client proxies for these services strip the “d” property so the end-user doesn’t need to know it was ever there.
ASP.NET和WCF的JSON服务端点实际上是用"d"属性将自己的JSON对象包裹起来,前段时间我有写一篇关于这个的问题的文章。虽然这听起来很奇怪,要访问数据必须要经过这个属性,实际上这是不优雅的缓解方式 通过所生成的客户端代理这些服务来去除“d”属性,以便最终的用户不需要知道这是曾经发生过的。
With ASP.NET MVC (and other similar frameworks), a significant number of developers are not using client generated proxies (we don’t have them) but instead using jQuery and other such libraries to call into these methods, making the “d” fix kind of awkward.
在ASP.NET MVC (和其他类似的框架)中,大部分开发人员没有使用客户端生成代理(我们不需要生成),而是使用jQuery和其他类似的库调用到这些方法,使“d”属性的问题变得有点儿尴尬。
What About Checking The Header?
检查报头怎么样?
Some of you might be wondering, “why not have the JSON service check for a special header such as the X-Requested-With: XMLHttpRequest or Content-Type: application/json before serving it up in response to a GET request?” I too thought this might be a great mitigation because most client libraries send one or the other of these headers, but a browser’s GET request in response to a script tag would not.
你们当中有些人可能会想, “为什么不在服务响应一个GET请求之前,让JSON服务做一个特殊的报头检查,例如 X-Requested-With:XMLHttpRequest或者Content-Type:application/json 这样的报头”。 我也认为这可能是一个很好的缓解方法,因为大多数的客户端库总会发送其中的一个报头,但浏览器响应script标签发送的GET请求则不会发送报头。
The problem with this (as a couple of co-workers pointed out to me) is that at some point in the past, the user may have made a legitimate GET request for that JSON in which case it may well be cached in the user’s browser or in some proxy server in between the victim’s browser and the vulnerable website. In that case, when the browser makes the GET request for the script, the request might get fulfilled from the browser cache or proxy cache. You could try setting No-Cache headers, but at that point you’re trusting that the browser and all proxy servers correctly implement caching and that the user can’t override that accidentally.
这样做的问题(如几个同事向我指出)是在过去的某个时刻,用户可能已经制造了一个获取JSON的合法GET请求,在这种情况下它(指前面说的合法GET请求)也许会在用户的浏览器中或者在受害者的浏览器和受攻击的网站之间的一些代理服务器中被缓存起来。在这种情况下,当浏览器从脚本中产生了GET请求,该请求可能会被浏览器缓存或代理缓存实现(指发送的GET请求所返回的数据是直接从缓存中获取)。你可以试下设置No-Cache报头,在设置了之后,你所信任的浏览器和所有的代理服务能正确执行缓存而且用户(应该是指用户发送的请求)不会不小心被覆盖。
Of course, this particular caching issue isn’t a problem if you’re serving up your JSON using SSL.
当然,如果你使用SSL来提供JSON,这种特殊的缓存问题就不是一个问题了。
The real issue?
真正的问题?
There’s a post at the Mozilla Developer Center which states that object and array initializers should not invoke setters when evaluated, which at this point, I tend to agree with, though a comment to that post argues that perhaps browsers really shouldn’t execute scripts regardless of their content type, which is also a valid complaint.
在Mozilla开发者中心有一篇帖子这样陈述:对象和数组初始化取值时不应该调用setters,而在这一点上,我表示同意,虽然文章中有产生争论的评论:浏览器或许真的不应该执行脚本,无论脚本有什么样的内容和类型,这也是一个有效的申诉。
But at the end of the day, assigning blame doesn’t make your site more secure. These type of browser quirks will continue to crop up from time to time and we as web developers need to deal with them. Chrome 2.0.172.31 and Firefox 3.0.11 were both vulnerable to this. IE 8 was not because it doesn’t support this method. I didn’t try it in IE 7 or IE 6.
但是最终来说,分配责任(应该是指上文中浏览器禁用脚本)并不会让您的网站更安全。这些类型的浏览器怪癖会继续不时地冒出来,我们作为Web开发人员需要处理它们。 Chrome浏览器2.0.172.31和Firefox 3.0.11都容易受到这个漏洞攻击。IE8不容易受到这个漏洞攻击因为它不支持这个方法。我没有在IE 7或者IE 6 浏览器上试验过。
It seems to me that to be secure by default, the default behavior for accessing JSON should probably be POST and you should opt-in to GET, rather than the other way around as is done with the current client libraries. What do you think? And how do other platforms you’ve worked with handle this? I’d love to hear your thoughts.
这种默认的情况在我看来是安全的:访问JSON的默认行为也许应该使用POST方式而选择性的使用GET方式 ,而不是在当前的客户端中使用其他的方式来访问JSON。你觉得如何呢?并且在其他平台上你工作中是如何处理这些问题呢?我很想听听你的想法。
In case you missed it, here are the repro steps again: grab the CodeHaacks solution from GitHub and run the JsonHijackDemo project locally (right click on the project and select Set as StartUp Project. Just follow the instructions on the home page of the project to see the attack in action. To see a successful attack, you’ll need to do this in a vulnerable browser such as Firefox 3.0.11.
如果你没有领会本文,这里有再现步骤:从GitHub上获取CodeHaacks这个项目解决方案,并运行JsonHijackDemo项目在本地(右键单击该项目并选择设为启动项目。只要按照该项目的主页上的说明就能看到整个攻击行动的流程。要看到一个成功的漏洞攻击流程,你需要使用容易受攻击(指支持__defineSetter__方法的)的浏览器,例如Firefox 3.0.11 。
I followed up this post with a proposal to fix JSON to prevent this particular issue.
我之后有发布《建议修复JSON》这篇文章来描述防止这一特别问题。
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/98348.html