20

挖洞经验 | 由Google Voice插件触发的DOM-XSS漏洞

 3 years ago
source link: https://www.freebuf.com/vuls/236884.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文讲述了作者在安装了Google Voice插件的环境中,通过Gmail接收邮件时偶尔发现的一个DOM XSS漏洞,经对Google Voice插件的源代码分析,最终找到了漏洞根源。漏洞获得了谷歌$3,133.7的奖励。

漏洞端倪

该全局性DOM XSS漏洞的发现也纯属偶然,当我构造了oneerror=alert(1)的XSS Payload进行发送测试,打开了Gmail邮箱收取Google Ads的信件时,突然在Gmail收件箱中跳出了以下弹窗:

eiayqaE.jpg!web

当时我的反应是,这是一个Google Ads规则触发的存在于Gmail中的存储型XSS,所以立马就想着上报,但仔细一分析,事情没这么简单。

漏洞分析

这里存在两方面的因素:我的系统中安装了Google Voice插件、XSS Payload-’444-555-4455 <img src=x onerror=alert(1)>’是显示在收件箱中的文本字段。

经过分析,我发现该XSS漏洞是由Google Voice插件引发的,在谷歌本身的用户相关网站accounts.google.com和其它第三方注册网站如facebook.com,都能有效触发并执行javascript代码。如下:

iiY3muz.jpg!webz2ARnaj.jpg!web 于是乎,我把Google Voice的源代码提取出来进行了一番分析,果然在其中的文件contentscript.js中,存在一个方法函数Wg(),就是它导致了DOM XSS。该函数如下:

function Wg(a) {

    for (var b = /(^|\s)((\+1\d{10})|((\+1[ \.])?\(?\d{3}\)?[ \-\.\/]{1,3}\d{3}[ \-\.]{1,2}\d{4}))(\s|$)/m, c = document.evaluate('.//text()[normalize-space(.) != ""]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null), d = 0; d < c.snapshotLength; d++) {

        a = c.snapshotItem(d);

        var f = b.exec(a.textContent);

        if (f && f.length) {

            f = f[2];

            var g = "gc-number-" + Ug,

                h = '<span id="' + g + '" class="gc-cs-link"title="Call with Google Voice">' + f + "</span>",

                k;

            if (k = a.parentNode && !(a.parentNode.nodeName in Og)) k = a.parentNode.className,

                k = "string" === typeof k && k.match(/\S+/g) || [], k = !Fa(k, "gc-cs-link");

            if (k) try {

                if (!document.evaluate('ancestor-or-self::*[@googlevoice = "nolinks"]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null)

                    .snapshotLength) {

                    if (0 == a.parentNode.childElementCount) {

                        var w = a.parentNode.innerHTML,

                            y = w.replace(f, h);

                        a.parentNode.innerHTML = y

                    } else {

                        w = a.data;

                        y = w.replace(f, h);

                        var u = Qc("SPAN");

                        u.innerHTML = y;

                        h = u;

                        k = a;

                        v(null != h && null != k, "goog.dom.insertSiblingAfter expects non-null arguments");

                        k.parentNode && k.parentNode.insertBefore(h,

                            k.nextSibling);

                        Vc(a)

                    }

                    var t = Ic(document, g);

                    t && (Ug++, nc(t, "click", ma(Sg, t, f)))

                }

            } catch (E) {}

        }

    }

}

上述代码理解起来不难,开发者试图从元素内容中查找抓取用户电话号码,然后使用抓取的电话号码作为其内容来创建另一个span元素,以便用户可以直接从网页中点击并实施呼叫操作。

仔细分解开来,在代码的第1行到第9行,用到了document.evaluate方法遍历元素内容,它可以在HTML和XML文档中进行搜索,并返回代表结果的实体XPathResult。也就是说,函数Wg()就是负责抓取元素内容的,它会把所有的文本结点赋值给变量 ‘a’,以此作为源进行后续处理查找,而以下代码就是导致DOM XPath注入的原因:

(var b = /(^|\s)((\+1\d{10})|((\+1[ \.])?\(?\d{3}\)?[ \-\.\/]{1,3}\d{3}[ \-\.]{1,2}\d{4}))(\s|$)/m, c = document.evaluate('.//text()[normalize-space(.) != ""]', a, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null), d = 0; d < c.snapshotLength; d++) {

        a = c.snapshotItem(d);

在上述代码之后,当函数Wg()继续执行搜索时,变量’b'会以正则方式在变量’a'中去匹配美国电话号码,如果有匹配结果,则赋值给变量 ‘f’,接下来,把它定义为变量’h'中的内容。

代码第10和11行主要是检查变量’f'中的HTML元素标记,它既不是形如SCRIPT, STYLE, HEAD, OBJECT, TEXTAREA, INPUT, SELECT, 或A这样的标记,也不是带有”gc-cs-link”的类属性名,它执行的检查目的在于:

1、防止插件与DOM混淆,因为它不想调用诸如SCRIPT、STYLE和HEAD之类的元素上的内容,也不希望用INPUT、SELECT等操作;
2、如果已经抓取到了电话号码,就停止代码,防止继续遍历循环。

代码第12行到27行中,存在一个if条件,如果其中的k变量为真,则说明找不到类属性名为gc-cs-link的内容,然后它会继续执行一个try声明,而在该try声明中的另一个if条件则会继续执行一个检查,如果找不到名为”googlevoice”的属性和名为”nolinks”的内容元素,则继续循环document.evaluate方法,检查变量’a'中的内容是否具备子元素,以下是其入口逻辑代码:

w = a.parentNode.innerHTML,

y = w.replace(f, h);

a.parentNode.innerHTML = y

接着,如果变量’a'中的内容不具备子元素,则将继续执行下一条语句:

k.parentNode && k.parentNode.insertBefore(h, k.nextSibling);

漏洞修复

我认为开发者可能是想执行变量 ‘f’中的内容,因为该内容中保存了由(innerHTML, insertBefore)方法获取的电话变量值,如’+12223334455′,但可能写代码的时候错误地执行了变量’a’ ,而在该变量中可以构造形如’444-555-4455 <img src=x onerror=alert(1)>’ 的XSS Payload,然后漏洞就发生了。

漏洞奖励

$3,133.7

*参考来源: missoumsai ,clouds 编译整理,转载请注明来自 FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK