- 英文原文:JSON Hijacking
- 英汉对照:JSON Hijacking 翻译对照
- 原文解析:JSON劫持漏洞分析和攻防演练
声明(阅读前必读!!!):
本人是一个英语渣,英语四级都没过。翻译此文存粹用于英语学习,而且大部分还是用翻译软件翻译的。请谨慎阅读此译文,注意不要被译文雷到。
另外这是一篇2009年的老文章了,里面有部分知识可能已经过时了,不过文章中关于JSON劫持的知识大部分还是十分有用的。在每段原文翻译后面我都会加上自己理解和相关备注,主要是本文开阔了一个思路。
由于文章翻译过来导致有些知识点不好理解,这里可以点击上面的JSON劫持漏洞分析和攻防演练,我会按照自己对JSON劫持的理解对这个漏洞进行讲述。
前阵子我写了篇关于一个微妙的JSON的漏洞(Anatomy of a Subtle JSON Vulnerability)可能导致敏感信息泄露的文章。这种特别的漏洞攻击(exploit)主要通过覆盖JavaScript数组的构造函数,从而劫持暴露的JSON数组数据,现在大多数的浏览器都不支持防御这些漏洞攻击。
PS: exploit的英文本意为“利用”。但是在计算机安全领域中指的是漏洞利用、利用程序或者网站的某个漏洞进行攻击!
这里解释下overriding the JavaScript Array(覆盖javascript数组的构造函数):因为javascript允许我们覆盖或重写其他的方法,包括Array()这种内部的方法,所以恶意攻击者可以很轻松的将js中的Array()方法替换和覆盖成恶意代码,相当于C#、JAVA和C++中的方法重写。原文作者使用的是__defineSetter__方法来实现这个功能的,但是此函数只有部门浏览器支持,所以一般来说还是直接使用JS的方法重写。
一般替换的恶意代码都是进行JSON劫持的代码,将获取到的JSON数据发送给第三方网站服务器上。之所以可以利用这点进行劫持,是因为JSON数组本身就是特殊的Array,网站一旦返回的JSON数组,恶意攻击者就通过重写Array()的构造函数将数据转发他们的服务上,这就是JSON数据劫持。
然而,还有另外一个与exploit相关的漏洞有可能会对更多的浏览器造成影响。最近在和微软的ScottHanselman交流时引起了我对这个问题的注意,并且我在上周的挪威开发者大会上演示了这个漏洞,虽然这个漏洞曾经出现在Twitter(推特)上。
PS:这个与exploit相关漏洞的描述地址:http://hackademix.net/2009/01/13/you-dont-know-what-my-twitter-leaks/
在我深入讨论前,让我首先给你讲解下形成JSON劫持漏洞的条件。
这个JSON劫持漏洞需要你暴露出JSON服务(指JSON服务地址被攻击者知道)并且进行如下操作:
- 返回敏感的数据
- 返回一个JSON数组
- 响应一个GET请求
- 发送请求的浏览器启用了JavaScript(很有可能)
- 发送请求的浏览器支持__defineSetter__方法
因此,如果你从不发送敏感的JSON格式数据,或者你仅仅发送POST请求以响应获取JSON等等操作,那么你的网站可能不会受到这个特定的漏洞的攻击(虽然有可能是其他人)。
我讨厌使用Visio(Microsoft Office Visio:绘制流程图的软件),但我想我会尽我所能的通过图表来更好的讲解这个过程。在第一个屏幕截图上,我们看到不知情的受害者登陆了这个容易受到攻击的网站,而这个易受攻击的网站向浏览器发送一个身份验证的cookie并且浏览器将它保存起来(浏览器会将这个HTTP的身份认证信息和相关的cookie缓存起来)。
在某些时候,无论是在过去,或者是在将来,总有一些坏人(恶意攻击者)不停的发送着大量的垃圾邮件(这里应该是钓鱼/欺诈邮件),受害者会收到一封内容是推荐一个非常有趣的视频的电子邮件,比如邮件中带有"钢琴上的松鼠"这样内容的一个视频链接。
但是这个链接实际上是指向这些恶意攻击者的网站(就是钓鱼网站),当受害人点击了这个链接,接下来会连续执行两个步骤。首先,受害人的浏览器会向恶意攻击者的网站发送一个GET请求。
这些恶意攻击者的网站(钓鱼网站)会响应并返回一些HTML语句,这些HTML语句包含着带有script标签的JavaScript语句(恶意脚本语句)。当浏览器运行到这些script标签时,它就会执行这段代码并向那些容易受到攻击的网站(这里假设攻击和劫持的网站是http://vulnerable.example.com/Json)发送另一个加载脚本的GET请求(如上图所示,这里会将从网站获取的响应文本放到/替换到script标签中,一般是返回JSON数据),该请求还会将受害人浏览器中的身份验证cookie一同发送过去。
攻击者通过伪装成受害者的浏览器,利用浏览器的授权(就是上面发送过去的身份验证cookie) 发出一个请求,这个请求可以到获取到拥有敏感数据的JSON数组。攻击者通过将请求的JSON数组载入到script中以替换重写成可执行的脚本语句,从而窃取到这些数据(脚本语句会将窃取到的数据发送给攻击者)。
为了能更深入的了解,看看这些演示攻击的实际代码,这样也许对我们理解有所帮助(你可以将代码下载拷贝下来并运行)。另外要注意的是下面的演示无论如何都不是专门针对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);
}
假如这是一个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请求将无法查看此敏感数据。
事实上,这是一个JSON数组是很重要的。事实证明,包含着一个JSON数组的脚本是一个有效的JavaScript脚本。一个仅包含着JSON对象的脚本不是有一个有效的JavaScript文件。例如,如果您有一个包含以下JSON的JavaScript文件:
{"Id":1, "Balance":3.14}
并且你有一个引用这个脚本文件的script标签:<script src=”http://example.com/SomeJson”></script>
你将会在HTML页面中得到一个JavaScript错误。然而,通过一个不幸的巧合,如果你有一个script标签,并且这个脚本标签引用文件的内容只有JSON数组,那么这(指的是script标签所引用文件中的JSON数组内容)将被认为是有效的JavaScript和数组被执行。
PS:如果JSON是以对象形式返回,即返回{"Id":1, "Balance":3.14},会提示非法标签错误。因为在JS中"{"和"}"这两个大括号(又称作花括号)在代码中是作为起始和结束的含义。所以为在JS文件中,单独的JSON数据要么以数组形式存在,要么在返回的对象上加小括号({"Id":1, "Balance":3.14}),要么返回JSON数组,否则就是非法标签错误。
现在让我们看看放置在这些攻击者服务器主机上的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>
这里发生了什么?没错,恶意攻击者改变了对象的原型,正是利用了__defineSetter__这个特殊方法在原型对象调用属性设置方法的时候进行覆盖。
在这个案例中,无论何时所有的对象都会设置一个属性名称为Id的字段(通过__defineSetter__方法),一个通过使用alert函数显示原型属性值的匿名函数将被调用和触发。注意,该脚本(上面使用alert函数显示属性值的脚本)可以很容易地替换成将数据上传给攻击者的脚本,敏感数据就是因此被泄露的。
正如我前面所说的(要实现上面的动作),攻击者需要你访问他的恶意网页,前提你刚刚登陆这个受攻击的页面不久并且页面中的会话仍然保持有效。最常见的钓鱼攻击伎俩就是通过发送一个包含恶意网站链接的邮件(诱骗受害人点击链接)。
如果你运气不好没注意到这个钓鱼链接,当你点击了这个链接时你仍然保持原来网站的登陆状态,那么在进入钓鱼网站后载入script标签的引用时,浏览器将会发送你的身份验证cookie至原来的网站。直到与原来网站链接上为止,你会发送一个包含有效身份验证的请求从而通过响应取得JSON数据,这些数据会获取到你的浏览器中。这可能听起来很熟悉,因为它真的是一个变种的跨站请求伪造(CSRF)攻击,之前我有写过相关的文章。
PS:这里原来的网站指的是受攻击的站点,载入的script标签指的是这段代码:
<script src="http://example.com/Home/AdminBalances"></script>
如果你想亲身感受一下,你可以从GitHub上获取这个CodeHaacks的解决方案,并在本地运行JsonHijackDemo项目(右键单击该项目,并选择设置为启动项目,只需按照项目主页上的说明进行攻击操作,它会告诉你要访问这个页面:http://demo.haacked.com/security/JsonAttack.html 。
请注意,这种攻击方式对IE8浏览器是无法起作用的,它会提示你 __defineSetter__不是一个有效的方法。上一次我测试的时候,在Chrome和Firefox都可以正常的起到作用。
避免措施是简单的。要么从不发送的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);
}
这个方法存在着一个问题,许多像JQuery这样的JavaScript类库默认发送JSON请求都是使用GET,而不是POST。例如,$.getJSON 默认就是发起一个GET请求。因此调用这个JSON的服务时,你需要确保你的客户端库是发出一个POST请求。
ASP.NET和WCF的JSON服务端点实际上是用"d"属性将自己的JSON对象包裹起来,前段时间我有写一篇关于这个的问题的文章。虽然这听起来很奇怪,要访问数据必须要经过这个属性,实际上这是不优雅的缓解方式 通过所生成的客户端代理这些服务来去除“d”属性,以便最终的用户不需要知道这是曾经发生过的。
在ASP.NET MVC (和其他类似的框架)中,大部分开发人员没有使用客户端生成代理(我们不需要生成),而是使用jQuery和其他类似的库调用到这些方法,使“d”属性的问题变得有点儿尴尬。
检查报头怎么样?
你们当中有些人可能会想, “为什么不在服务响应一个GET请求之前,让JSON服务做一个特殊的报头检查,例如 X-Requested-With:XMLHttpRequest或者Content-Type:application/json 这样的报头”。 我也认为这可能是一个很好的缓解方法,因为大多数的客户端库总会发送其中的一个报头,但浏览器响应script标签发送的GET请求则不会发送报头。
这样做的问题(如几个同事向我指出)是在过去的某个时刻,用户可能已经制造了一个获取JSON的合法GET请求,在这种情况下它(指前面说的合法GET请求)也许会在用户的浏览器中或者在受害者的浏览器和受攻击的网站之间的一些代理服务器中被缓存起来。
在这种情况下,当浏览器从脚本中产生了GET请求,该请求可能会被浏览器缓存或代理缓存实现(指发送的GET请求所返回的数据是直接从缓存中获取)。你可以试下设置No-Cache报头,在设置了之后,你所信任的浏览器和所有的代理服务能正确执行缓存而且用户(应该是指用户发送的请求)不会不小心被覆盖。
当然,如果你使用SSL来提供JSON,这种特殊的缓存问题就不是一个问题了。
真正的问题?
在Mozilla开发者中心有一篇帖子这样陈述:对象和数组初始化取值时不应该调用setters,而在这一点上,我表示同意,虽然文章中有产生争论的评论:浏览器或许真的不应该执行脚本,无论脚本有什么样的内容和类型,这也是一个有效的申诉。
但是最终来说,分配责任(应该是指上文中浏览器禁用脚本)并不会让您的网站更安全。这些类型的浏览器怪癖会继续不时地冒出来,我们作为Web开发人员需要处理它们。 Chrome浏览器2.0.172.31和Firefox 3.0.11都容易受到这个漏洞攻击。
IE8不容易受到这个漏洞攻击因为它不支持这个方法。我没有在IE 7或者IE 6 浏览器上试验过。
这种默认的情况在我看来是安全的:访问JSON的默认行为也许应该使用POST方式而选择性的使用GET方式 ,而不是在当前的客户端中使用其他的方式来访问JSON。你觉得如何呢?并且在其他平台上你工作中是如何处理这些问题呢?我很想听听你的想法。
如果你没有领会本文,这里有再现步骤:从GitHub上获取CodeHaacks这个项目解决方案,并运行JsonHijackDemo项目在本地(右键单击该项目并选择设为启动项目。只要按照该项目的主页上的说明就能看到整个攻击行动的流程。要看到一个成功的漏洞攻击流程,你需要使用容易受攻击(指支持__defineSetter__方法的)的浏览器,例如Firefox 3.0.11 。
我之后有发布《建议修复JSON》这篇文章来描述防止这一特别问题。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/98347.html