JSON劫持漏洞攻防原理及演练

  注* 做者发表这篇文章的时间较早,某些方法可能并非最好的解决方案,但针对这种漏洞进行的攻击还依然可见,如早期的:QQMail邮件泄露漏洞,下面介绍的是对这种攻击原理的介绍。javascript

  不久以前,我写了一篇文章《一个微妙的JSON漏洞》,文中讲到这个漏洞可能会致使敏感信息泄露。针对该漏洞的特色,经过覆盖JavaScript数组构造函数以窃取(暴露)JSON返回数组,而如今大多数浏览器还没法防范这种攻击。html

  然而,经过和微软的Scott Hanselman交流,我了解到另一个方法可能会影响更多的浏览器。在上周的挪威开发者大会上,我作了一个针对Json劫持漏洞的演示。java

  在我进一步讲以前,我先说一说,这个漏洞可能带来的影响。ajax

  在如下条件下,会出现这个漏洞:首先暴露JSON服务,而且该服务会返回敏感数据;返回JSON数组;对GET请求作出响应;发送这个请求的浏览器启用了JavaScript而且支持_defineSetter_方法。json

  若是咱们不使用JSON发送敏感数据,或者只对报文请求作出响应,那么咱们的网站就不存在这个漏洞。数组

  我不喜欢用流程图展现这个过程,我会尽可能用图表描述。在第一页截图上,咱们能够看到不知情的受害者登录漏洞网站,漏洞网站返回了一个身份认证的cookie。浏览器

  咱们均可能收到过一些垃圾邮件,邮件中附有连接,发送者声称这有一段搞笑视频。这些大量的垃圾邮件都是一些别有用心的人发的。缓存

  可是实际上,这些连接指向的是那些坏家伙本身网站。当咱们点击了连接,接下来的两个步骤会迅速进行。第一,咱们的浏览器向这些网站发送请求。安全

  

  第二,那些网站会响应一些包含JavaScript的HTML。这些JavaScript会带一个script标记。当浏览器检测到script标记,它就会向那些漏洞网站再发一个下载脚本的GET请求,携带着身份验证的cookie。服务器

  这样,那些坏家伙就假装成了受害者的浏览器,利用其身份发出了一个包含敏感数据的JSON请求。接着,把JSON加载为可执行的JavaScript,这样以来,那些黑客就可以获取到这些数据。
为了加深理解,咱们能够看看一个攻击的实际代码。假如漏洞网站返回带有敏感数据的JSON响应经过以下方式发送:

[Authorize]
publicJsonResultAdminBalances(){
    varbalances=new[]{
        new{Id=1,Balance=3.14},
        new{Id=2,Balance=2.72},
        new{Id=3,Balance=1.62}
    };
    returnJson(balances);
}

  须要说明的是,上面的演示不是专门针对ASP.NET或者ASP.NET MVC,我仅仅是恰巧用ASP.NET MVC来演示这个漏洞而已。
假如这是HomeController的一种方法,咱们经过对/Home/AdminBalances发送了一个GET请求,而且返回以下JSON文本:

[{“Id”:1,”Balance”:3.14},{“Id”:2,”Balance”:2.72},{“Id”:3,”Balance”:1.62}]

  注意,我定义这个方法时使用了Authorize属性,用来验证请求者的身份。因此一个匿名的GET请求将不会获得敏感数据。
重要的是:这是一个JSON数组。包含JSON数组的文本是一个有效的JavaScript脚本,而且能够被执行。仅仅包含JSON对象的脚本不是一个有效的JavaScript可执行文件。
举个例子,若是咱们有一个包含以下JSON代码的JavaScript文档:

{“Id”:1,”Balance”:3.14}

  而且有一个指向这个文档的脚本标签:

<script src=”http://example.com/SomeJson”></script>

  这样,咱们会在HTML页中获得一个JavaScript错误。然而,假若存在一个不幸的巧合,若是咱们有一个script标签,这个标签指向仅仅含有一个JSON数组的文档,这样的话,这个标签就会被误认为是有效的JavaScript,而且数组会生效。

  下面就让咱们看看那些别有用心的人的服务器上的HTML页。

  注*这里咱们能够看到使用 Json Object 而不是Json Array返回你的数据,能够在必定程度上预防这种漏洞。

<html>
...
<body>
<scripttype="text/javascript">
Object.prototype.__defineSetter__('Id',function(obj){alert(obj);});
</script>
<scriptsrc="http://example.com/Home/AdminBalances"></script>
</body>
</html>

  看到了什么?黑客正在改变对象的原型,用_defineSetter_这种特殊方法,覆盖JSON对象(Object)本来应有的默认行为。

  在这个例子中,一个命名合适的ID在任什么时候候均可以被设置到任何对象上时,一个匿名的函数将会被调用,这个函数将会利用alert 函数显示属性值。注意,这时脚本仅仅会将数据发回给那些坏家伙,而不会发送敏感数据。

  就像以前提到的,坏家伙须要使咱们在登陆漏洞网站后不久而且在会话仍旧有效时,访问他的恶意的网页。经过含有恶意网站连接的邮件进行钓鱼攻击的方式是很典型的。

  若是你仍旧点击连接登陆原网站,浏览器将会在加载脚本标签中引用的脚本并向网站发送你的身份验证cookie。直到链接上原网站,咱们对JSON数据发出一 个有效的身份验证请求,而且会收到在咱们浏览器中响应的有效数据。这些话可能听着很熟悉,由于它是一个真的变种伪造跨站请求,以前我写过这种状况。

  由于在IE8上_defineSetter_是一个无效的方法,因此在IE8上看不到现象。我在Chrome和Firefox上都试过,均可以。

  避免这个漏洞也很简单:或者从不发送JSON数组,或者只访问HTTP POST以得到须要的数据。举个例子:在ASP.NET MVC中,你能够用AcceptVerbsAttribute来实现:

[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
publicJsonResultAdminBalances(){
    varbalances=new[]{
        new{Id=1,Balance=3.14},
        new{Id=2,Balance=2.72},
        new{Id=3,Balance=1.62}
    };
    returnJson(balances);
}

  这个方法的一个问题就是:像jQuery这样的不少JS库默认都是用GET方式发送JSON请求,而不是POST。举个例子,$.getJSON 默认发起的是GET请求。因此,当进行这种JSON访问时,咱们须要确信咱们是用客户端库发起的POST请求。

  ASP.NET和WCF JSON服务端实际上在对象中用了“d”属性,包裹了他们的JSON,这我在另外一篇文章中讨论过:

  必须通过这些属性得到数据,彷佛有些奇怪,但这须要经过一个客户端代理来实现来去除“d”属性,,以便不影响最终用户。

  在ASP.NET MVC下,绝大多数开发者没有生成客户端代理,而是使用jQuery和其余相似的库,这样一来使用“d”属性的就有些尴尬。

  注*其实MVC的方法有点复杂,到这里咱们能够看出,JSON劫持漏洞是要以在受豁者的浏览器上执行JSON返回对象为前提的,其实Google使用了一种更加聪明的方法,经过添加“死循环”命令,防止黑客运行这段脚本,可参见这篇文章:为何谷歌的JSON响应以while(1);开头? 

  检查首部(Http-Header)怎么样?

  一些人可能会有疑问:“为何不在响应一个GET请求以前,用一个特殊的首部对JSON服务进行检查?就像X-Requested- With:XMLHttpRequest或者Content-Type:application/json”。我认为这多是个过渡,由于大多数的客户端 库会发送一种或两种Header,可是浏览器响应脚本标签的GET请求不这样。

  问题是:在过去某个时候,用户可能会发出合法的JSON GET请求,在这种状况下,这个漏洞可能会隐藏在用户浏览器的正常请求之间。也是在此种状况下,当浏览器发出GET请求,这种请求可能会缓存在浏览器和代 理服务器的缓冲区。咱们能够尝试着设置No-Cache header,这样,咱们信任浏览器和全部的代理服务器可以正确地实现高速缓存而且信任用户也不会被意外地覆盖。

  固然,若是咱们用SSL提供JSON文本,这个特定的缓存问题将会很容易被解决。

  注*这里咱们能够看到防范方法这三:不要cache你的ajax请求,不过目前彷佛全部的js库默认都是不cache的。

  真正的问题在哪里?

  Mozilla Developer Center发表的一篇文章中写道:对象和数组初始化设定项在赋值时不该该调用setters方法。这一点我赞成。尽管有评论认为:也许浏览器真的不该该执行脚本。

  可是在一天结束的时候,分派责任并不能使你的网站更加安全。这些浏览器的怪癖毛病将会时不时地出现。咱们做为网站的开发者须要解决这些问题。 Chrome2.0.172.31和Firefox3.0.11也有这个软肋。IE8没有这个问题,由于它不支持这种方法,我也没有在IE7或者IE6中 试验过。

  在我看来,在当前的客户端库下,安全访问JSON的默认方式应该是POST,而且咱们应该选择GET,而不是其余方式。您以为呢?您所了解的其余平台是怎么解决这个问题的呢?我很想听听你们的想法。

  原文 haacked.com

相关文章
相关标签/搜索