利用 onload 事件監控跨站資源

說到跨站資源監控,首先會聯想到『Content Security Policy』。既然 CSP 好用,咱們何須本身再搞一套呢。那就先來吐槽下 CSP 的缺陷。html

目前的 CSP

日誌不詳細

用過 CSP 的都很鬱悶,上報的只有違規的站點名,卻沒有具體路徑。這是缺陷,仍是特地的設計?前端

顯然,CSP 是爲安全定製的,裏面的規範天然要嚴格制定,不然就會帶來新的安全問題。若是支持詳細路徑的上報,那又會引出什麼問題?jquery

因爲 CSP 會上報全部的請求,甚至包括重定向的,所以能夠用來探測重定向後的地址。假如已登陸的用戶訪問 login.xx.com 會重定向到 xx.com/username,那麼攻擊者設計一個只容許重定向前的規則的頁面,用戶訪問後,重定向後的 URL 就會當作違規地址上報給攻擊者,這其中就包括了用戶名。後端

若是支持詳細路徑的上報,這簡直就是災難,就用來探測的用戶隱私信息了。事實上目前只上報主機名,都能進行一些利用,例如這篇 Using Content-Security-Policy for Evil瀏覽器

不過新的規範老是在改進,將來也許只上報重定向前的 URL。但在這以前,咱們只能接受這些雞肋的上報日誌。緩存

規則不靈活

CSP 目前只支持白名單列表,這多少有些死板。安全

更糟的是,不一樣規則之間沒法繼承和共享。例如默認有個 default-src 規則,但其餘的規則會覆蓋它,而不是繼承它。這就致使各個規則之間,出現不少的重複,使得整個字符串變的冗長。網絡

沒法和頁面交互

CSP 的監控和上報,是在瀏覽器後臺自動處理的,沒有提供一個事件供頁面進行交互。app

這樣就只能使用統一方式強制處理了,而沒法交給頁面腳本,更好的來自定義處理。框架

上報方式不可控

若是處理方式有多種選擇,那麼統一處理也無可厚非。

但事實上 CSP 的上報方式及格式,沒有任何可選餘地。只能使用 POST + JSON 的方式提交,而且其中的字段十分累贅,甚至把規則裏的白名單列表也發上來了。

此外,也沒法設定一個緩存時間,控制重複上報的間隔。在配置白名單遺漏時,會出現大量的誤報,嚴重消耗資源。

浪費帶寬

在較新的 Chrome 裏,可以使用 meta 標籤在前端頁面定義 CSP 規則,但其餘瀏覽器目前仍不支持。

爲了可以統一,大多仍使用 HTTP 頭部輸入的方式。因爲規則一般都很長,致使每次頁面訪問,都會額外增長數百字節。

維護繁瑣

若是是經過 Web 服務開啓的,那麼每次調整策略,都得修改配置甚至重啓服務,非常麻煩。

兼容性不高

目前只有高版本的瀏覽器支持,而 IE 系列的則幾乎都沒能很好的支持。

若是某些攻擊只爭對低版本的瀏覽器,那麼頗有可能出現大量遺漏。

模擬的 CSP

原理

事實上在 CSP 出現的好幾年前,就有一個可以監控跨站資源的方案,下面就來分享下。

寫過 JS 的都知道,若是須要給大量元素監聽事件,無需對每一個元素上都進行綁定,只要監聽它們的容器便可。當具體的事件冒泡到容器上,經過 event.target 便可獲知是哪一個元素產生的。

腳本、圖片、框架等元素加載完成時,都會產生 onload 事件;而全部元素都位於『文檔』這個頂級容器。所以咱們監聽 document 的 onload 事件,便可獲知全部加載資源的元素。

不過 onload 這個事件比較特殊,沒法經過冒泡的方式來監聽。但在 DOM-3 標準模型裏,事件還有一個『捕獲』的概念,這也是爲何 addEventListener 有第三個參數的緣由。

咱們經過事件捕獲機制,將其拿下,從而監控文檔級別的全局 onload 事件。



<script> document.addEventListener('load', function(e) { console.log(e.target); }, true); </script> <script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script> <iframe src="http://www.baidu.com/"></iframe>

clipboard.png

相似的,若是資源加載失敗,會觸發 onerror 事件。咱們也可同時將其捕獲,跟蹤那些暫時不可用的跨站資源。

優點

經過腳本的方式,就能夠更靈活的處理問題了。規則的黑白名單,上報方式、格式等等,均可以本身來定義。最重要的是,咱們可以得到詳細的違規 URL 了!

相比後端配置,前端腳本更新維護起來容易的多。並且得益於瀏覽器緩存,無需每次更新配置,節省不少資源。

加強

也許你已經發現了,這隻能實現 CSP 的 Report-Only 功能,根本沒法進行攔截。並且其餘的內聯事件、網絡通訊等也沒有涉及。

不過本文的主題已經說了,只是監控跨站資源而已,並不攔截。事實上,若是可以作到及時發現問題,就很不錯了。

若是非得經過 JS 來實現攔截功能,能夠參考以前的『XSS 前端防火牆系列』:

雖然能夠更嚴格的防禦,但實現起來更臃腫,性能開銷也更大。要是攔截了正常的業務功能,形成的損失會更大。

而使用如何這個小技巧,只需幾行代碼便可實現,性能消耗忽略不計。

缺陷

固然,那麼簡單的方案確定沒法全面。

因爲咱們只監聽文檔容器,有些還未加入到文檔的元素產生的事件,咱們就沒法捕獲到了。最典型的就是:

new Image().src = '...'

雖然建立的 HTMLImageElement 對象具備 onload 事件,可是此時還只是一個離屏元素,事件就沒法對外傳播了。

若是非得解決,只能經過函數鉤子的方式監控 URL。

此外,IE9 如下的瀏覽器不支持 DOM-3,而 attachEvent 是沒法設置捕獲的,所以咱們還需一個後備方案。畢竟國內低版本 IE 用戶仍有很多,即便能實現部分功能,也勝於無。

後備

事實上,即便主流瀏覽器,有些特殊元素並無 onload 事件,例如 Flash 插件。

爲了彌補這些不足,同時儘量保持簡單高效,咱們使用定時輪詢的方式,對特定元素進行掃描。這裏使用一個你們都知道,但未必都清楚的方法:document.getElementsByTagName。

這個功能都知道,但返回的類型或許不多琢磨。他返回的並非一個 Array,也不是 NodeList,而是 HTMLCollection。

W3C 規範描述中,有一個顯眼的詞 『live』,已經道出了這個接口的獨特之處——它是一個動態的集合,能隨着容器內元素的增減而變化。

clipboard.png

所以,咱們事先映射出文檔容器內的元素,以後便可隨時查詢集合了。

<button id="btn">Load Script</button>


<div id="stat"></div>




<script>
    function log(str) {
        var line = document.createElement('div');
        line.innerHTML = str;
        stat.appendChild(line);
    }

    var colScript = document.getElementsByTagName('script');

    setInterval(function() {
        var display = [];

        for (var i = colScript.length - 1; i >= 0; i--) {
            var el = colScript[i];
            if (el.src) {
                // check url
                display.push(el.src);
            }
        }
        log('num: ' + display.length + ' list:' + display.join(',') );
    }, 1000);

    // [test] load script
    btn.onclick = function() {
        var el = document.createElement('script');
        el.src = 'http://libs.baidu.com/jquery/1.9.0/jquery.js';
        document.body.appendChild(el);
    }
</script>

clipboard.png

除了 SCRIPT,咱們還能夠監控全部存在風險隱患的元素,例如:EMBED,OBJECT,IFRAME 等等。這樣即便是低版本的 IE 用戶,也能參與預警上報了,比起徹底沒有好的多。

固然,這種簡單方法也很容易被繞過。若是腳本刪除了自身元素,那麼咱們就沒法跟蹤到了。而腳本一旦運行就已在內存裏,即便元素節點被移除,仍然能繼續運行。

不過,對於通常的狀況也足夠應對了。一般運行商的廣告劫持,大多都很落後。即便要進行後期對抗,也能夠利用以前的前端防火牆,使用嚴格的方案。

擴展信息

見過 CSP 日誌的大多都會很困惑,出現的這些違規資源,究竟位於頁面何處。因爲沒有確切的細節,給排查工做帶來很大困難。

畢竟,絕大多數的上報問題,都是沒法復現的。它們要麼是運行商的廣告,或者是瀏覽器插件。難很經過這些日誌,來定位問題所在。

既然現在使用本身的腳原本實現,理應帶上一些有意義的信息。除了資源類型和詳細 URL,咱們還須要一個可以定位問題所在的參數——DOM 路徑。

clipboard.png

將每層元素的 #id 和 .class 跟隨標籤名,獲得一個標準的 CSS 選擇器。經過它,便可很是容易的定位到違規元素,同時也能用於統計和分析。

例如出現頂級元素就是 SCRIPT 的,顯然這是一個被插入到 <html> 以外的腳本,頗有可能就是運營商注入的廣告腳本。

例如出現 HTML > BODY > ... > DIV.editor-post > SCRIPT 這樣帖子容器裏的腳本,那麼極有多是出現 XSS 了。正常狀況下,用戶內容區域並不會出現腳本元素。

clipboard.png

經過詳細的上報日誌不斷學習,後端便可愈來愈精準的分析出問題所在。

而這些,目前的 CSP 難以實現。

上報方式

吸收 CSP 報告的不足之處,咱們使用更靈活的方式。

對於運營商廣告那樣的跨站資源,反覆上報一樣的信息,是毫無心義的,只會浪費帶寬資源。對於同一類警告,必定時間內上報一次就後夠了。

利用 URL、DOM 路徑等信息,能夠更容易的將日誌進行客戶端去重。經過本地存儲的記錄,就沒必要每次都上報了,在本地記錄違規的次數便可。

對於不嚴重的警告,本地也能夠累計到必定的數量再上報。這樣多條日誌一次發送,便可大幅減輕後端壓力。甚至還能夠考慮壓縮內容,節省網絡帶寬。

只有讓前端進行負載維護,纔不至於大規模部署的場合下,接收端被海量的誤報日誌拖垮。

總結

儘管 CSP 的初衷很美好,但到現在,仍只是能用,並無作到好用。所以,實際面臨的問題,仍是得靠本身來解決。

固然,標準的制定原本就是受益於大衆的,咱們也但願 CSP 標準能發展的愈來愈好用。

轉自:利用 onload 事件監控跨站資源

相關文章
相關標籤/搜索