避免博客长草,水一篇文章,这篇文章中主要讲一个在前端中出现的有意思的变量劫持漏洞。
0x1 基础知识
当页面存在iframe
的时候,父页面和子页面是可以相互获取到对方的window
对象的,主要利用下面的方法。
(本文不考虑 iframe
的 sandbox
属性,所有测试都是在不添加任何sandbox
的限制下进行。)
父访问子:
1 | document.getElementById("iframe1").contentWindow; // 获取iframe的window对象 |
子访问父:
1 | window.parent; //获取上一级的window对象,如果还是iframe则是该iframe的window对象 |
如果父和子页面是同源的,那么可以通过这个window对象获取到任何你想获取的内容,包括但是不限于 document,name,location 等。但是在非同源的情况下,iframe的window对象大多数的属性都会被同源策略block掉,但是有两个属性比较特殊。
- frames 可读,但是不可写。 意味着可以读取不同域的子页面里面的iframe的window对象
- location 可写,但是不可读。意味着父子可以相互修改彼此的 location
结合以上两点可以推导出,爷可以修改孙(孙可以修改爷)的location。(父页面可以获取子页面的window对象,然后通过frames获取孙页面的window对象,然后修改location)
爷修改孙,演示如下:
1 | <!-- localhost:80/index.html --> |
1 | <!-- localhost:8888/view.html --> |
0x2 重新审视一下 id 属性
我们知道在浏览器中有如下特点,我们定义的所有全局变量,都被存储在window对象中,作为window的属性来被访问的。
下面在console中验证一下:
1 | content = "i am content storage in window"; |
同样,我们在页面中定义的具有id属性的dom对象也是作为全局变量存储在 window 中的。
1 | <h1 id="test"></h1> |
然后再console里访问一下:
1 | test |
这时候想到一个问题,既然 id 属性会被注册成全局变量,那么它会不会覆盖掉已经存在的全局变量呢?我们写如下的测试代码:
1 | <h1>test</h1> |
在console中输入:
1 | test |
事实证明无法覆盖已经定义的变量,但是却可以定义新的变量
怎么让页面中出现未定义的全局变量呢?别忘了 chrome 74之后 默认的 xss auditor 从block模式变成了filter模式,可以利用这个删除掉页面中的代码。(此问题文章最后演示)
另外我们知道,如果在页面中定义两个id一样的元素之后,这样使用 document.getElementById
就无法获取到这个id了,但是并不意味着着全局变量就不存在了,看下面这个实验。
1 | <h1 id="test"></h1> |
1 | test |
很明显全局变量test
还是存在的,是包含两个元素的数组。
0x3 同样道理看一下iframe的name属性
1 | <iframe name="viewer" src="./view.html"></iframe> |
在console里验证一下
1 | viewer |
情况差不多,这里的 viewer
是注册在全局变量里的window对象。
但是如果页面中出现两个name
相同的iframe
,又会是什么情况呢?
1 | <iframe name="test" src="http://B.com/B.html" ></iframe> |
在console里面输入:
1 | > test |
发现跟id的情况并不相同,这里只有第一个元素,而且仅有第一个元素。
0x4 id 和 name 重复出现时
name在id前面
1 | <iframe name="test" src="http://B.com/B.html" ></iframe> |
1 | test |
id在name前面
1 | <h1 id="test"></h1> |
1 | test |
可以发现 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模式产生的),怎么利用?场景用代码描述如下:
1 | <!-- A.com/A.html --> |
1 | <!-- B.com/B.html --> |
利用的poc如下,修改A.html如下:
1 | <script> |
然后访问 A.com/A.html ,就会发现 B.com/B.html 中的 VUL
已经被劫持了。
0x6 进一步利用
由于我们控制了 B.com/B.html
中的 iframe 指向了 http://A.com/index.html
, 所以此时 B 想访问 VUL
对象的子属性是不行的,因为是跨域的。
比如 B.com/B.html
有如下代码:
1 | function test(){ |
为了解决这个问题,我们还可以在 http://A.com/index.html
这个页面上做文章,做法就是再插入一个iframe
1 | <!-- http://A.com/index.html --> |
此时在 B.com 访问 VUL.config
就不会报错,而且访问 config.name
时,如果 http://B.com/C.html
存在这个全局变量就不会报错,因为此时 B.com
访问 VUL.config
的一切属性都不会报错了,因为没有跨域。