欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

XSS 前端防火墻 —— 天衣無縫的防護

系統(tǒng) 1667 0

上一篇 講解了鉤子程序的攻防實戰(zhàn),并實現(xiàn)了一套對框架頁的監(jiān)控方案,將防護作用到所有子頁面。

到目前為止,我們防護的深度已經(jīng)差不多,但廣度還有所欠缺。

例如,我們的屬性鉤子只考慮了 setAttribute,卻忽視還有類似的 setAttributeNode。盡管從來不用這方法,但并不意味人家不能使用。

例如,創(chuàng)建元素通常都是 createElement,事實上 createElementNS 同樣也可以。甚至還可以利用現(xiàn)成的元素 cloneNode,也能達到目的。因此,這些都是邊緣方法都是值得考慮的。

下面我們對之前討論過的監(jiān)控點,進行逐一審核。

內(nèi)聯(lián)事件執(zhí)行 eval

在第一篇文章結(jié)尾談到,在執(zhí)行回調(diào)的時候,最好能監(jiān)控 eval,setTimeout('...') 這些能夠解析代碼的函數(shù),以防止執(zhí)行儲存在其他地方的 XSS 代碼。

先來列舉下這類函數(shù):

  • eval

  • setTimeout(String) / setInterval(String)

  • Function

  • execScript / setImmediate(String)

事實上,利用上一篇的鉤子技術(shù),完全可以把它們都監(jiān)控起來。但現(xiàn)實并沒有我們想象的那樣簡單。

eval 重寫有問題嗎

eval 不就是個函數(shù),為什么不可以重寫?

        
          
            var
          
          
            raw_fn
          
          
            =
          
          
            window
          
          
            .
          
          
            eval
          
          
            ;
          
          
            window
          
          
            .
          
          
            eval
          
          
            =
          
          
            function
          
          
            (
          
          
            exp
          
          
            )
          
          
            {
          
          
            alert
          
          
            (
          
          
            '執(zhí)行eval: '
          
          
            +
          
          
            exp
          
          
            );
          
          
            return
          
          
            raw_fn
          
          
            .
          
          
            apply
          
          
            (
          
          
            this
          
          
            ,
          
          
            arguments
          
          
            );
          
          
            };
          
          
            console
          
          
            .
          
          
            log
          
          
            (
          
          
            eval
          
          
            (
          
          
            '1+1'
          
          
            ));
          
        
      

完全沒問題啊。那是因為代碼太簡單了,下面這個 Demo 就可以看出山寨版 eval 的缺陷:

        
          
            (
          
          
            function
          
          
            ()
          
          
            {
          
          
            eval
          
          
            (
          
          
            'var a=1'
          
          
            );
          
          
            })();
          
          
            alert
          
          
            (
          
          
            typeof
          
          
            a
          
          
            );
          
        
      

Run

按理說應(yīng)該 undefined 才對,結(jié)果卻是 number。局部變量都跑到全局上來了。這是什么情況?事實上,eval 并不是真正意義的函數(shù),而是一個關(guān)鍵字!想了解詳情 請戳這里

Function 重寫有意義嗎

Function 是一個全局變量,重寫 window.Function 理論上完全可行吧。

        
          
            var
          
          
            raw_fn
          
          
            =
          
          
            window
          
          
            .
          
          
            Function
          
          
            ;
          
          
            window
          
          
            .
          
          
            Function
          
          
            =
          
          
            function
          
          
            ()
          
          
            {
          
          
            alert
          
          
            (
          
          
            '調(diào)用Function'
          
          
            );
          
          
            return
          
          
            raw_fn
          
          
            .
          
          
            apply
          
          
            (
          
          
            this
          
          
            ,
          
          
            arguments
          
          
            );
          
          
            };
          
          
            var
          
          
            add
          
          
            =
          
          
            Function
          
          
            (
          
          
            'a'
          
          
            ,
          
          
            'b'
          
          
            ,
          
          
            'return a+b'
          
          
            );
          
          
            console
          
          
            .
          
          
            log
          
          
            (
          
          
            add
          
          
            (
          
          
            1
          
          
            ,
          
          
            2
          
          
            )
          
          
            );
          
        
      

重寫確實可行。但現(xiàn)實卻是不堪一擊的:因為所有函數(shù)都是 Function 類的實例,所以訪問任何一個函數(shù)的 constructor 即可得到原始的 Function。

例如 alert.constructor,就可以繞過我們的鉤子。甚至可以用匿名函數(shù):

        
          
            (
          
          
            function
          
          
            (){}).
          
          
            constructor
          
        
      

所以,F(xiàn)unction 是永遠鉤不住的。

額外的執(zhí)行方法

就算不用這類函數(shù),仍有相當(dāng)多的辦法執(zhí)行字符串,例如:

  • 創(chuàng)建腳本,innerHTML = 代碼

  • 創(chuàng)建腳本,路徑 = data:代碼

  • 創(chuàng)建框架,路徑 = javascript:代碼

  • ......

看來,想完全把類似 eval 的行為監(jiān)控起來,是不現(xiàn)實的。不過作為預(yù)警,我們只監(jiān)控 eval,setTimeout/Interval 也就足夠了。

可疑模塊攔截

第二篇談了站外模塊的攔截。之所以稱之『模塊』而不是『腳本』,并非只有腳本元素才具備執(zhí)行能力。框架頁、插件都是可以運行代碼的。

可執(zhí)行元素

我們列舉下,能執(zhí)行遠程模塊的元素:

  • 腳本
        
          
            <script 
          
          
            src=
          
          
            "..."
          
          
            />
          
        
      
  • 框架
        
          
            <iframe
          
          
            src=
          
          
            "..."
          
          
            >
          
          
            <frame
          
          
            src=
          
          
            "..."
          
          
            >
          
        
      
  • 插件(Flash)
        
          
            <embed
          
          
            src=
          
          
            "..."
          
          
            >
          
          
            <object
          
          
            data=
          
          
            "..."
          
          
            >
          
          
            <object><param
          
          
            name=
          
          
            "moive|src"
          
          
            value=
          
          
            "..."
          
          
            ></object>
          
        
      
  • 插件(其他)
        
          
            <applet
          
          
            codebase=
          
          
            "..."
          
          
            >
          
        
      

這些元素的路徑屬性,都應(yīng)該作為排查的對象。

不過,有這么個元素的存在,可能導(dǎo)致我們的路徑檢測失效,它就是:

        
          
            <base
          
          
            href=
          
          
            "..."
          
          
            >
          
        
      

它能重定義頁面的相對路徑,顯然是不容忽視的。

事實上,除了使用元素來執(zhí)行站外模塊,還可以使用網(wǎng)絡(luò)通信,獲得站外的腳本代碼,然后再調(diào)用 eval 執(zhí)行:

AJAX

目前主流瀏覽器都支持跨域請求,只要服務(wù)端允許就可以。因此,我們需監(jiān)控 XMLHttpRequest::open 方法。如果請求的是站外地址,就得做策略匹配。不通過則放棄向上調(diào)用,或者拋出一個異常,或者給 XHR 產(chǎn)生一個 400 狀態(tài)。

WebSocket

WebSocket 和 XHR 類似,也能通過鉤子的方法進行監(jiān)控。

不過,值得注意的是,WebSocket 并非是個函數(shù),而是一個類。因此,在返回實例的時候, 別忘了將 constructor 改成自己的鉤子,否則就會泄露原始接口

        
          
            var
          
          
            raw_class
          
          
            =
          
          
            window
          
          
            .
          
          
            WebSocket
          
          
            ;
          
          
            window
          
          
            .
          
          
            WebSocket
          
          
            =
          
          
            function
          
          
            WebSocket
          
          
            (
          
          
            url
          
          
            ,
          
          
            arg
          
          
            )
          
          
            {
          
          
            alert
          
          
            (
          
          
            'WebSocket 請求:'
          
          
            +
          
          
            url
          
          
            );
          
          
            var
          
          
            ins
          
          
            =
          
          
            new
          
          
            raw_class
          
          
            (
          
          
            url
          
          
            ,
          
          
            arg
          
          
            );
          
          
            // 切記
          
          
            ins
          
          
            .
          
          
            constructor
          
          
            =
          
          
            WebSocket
          
          
            ;
          
          
            return
          
          
            ins
          
          
            ;
          
          
            };
          
          
            var
          
          
            ws
          
          
            =
          
          
            new
          
          
            WebSocket
          
          
            (
          
          
            'ws://127.0.0.1:1000'
          
          
            );
          
        
      

另外,因為它是一個類,所以不要忽略了靜態(tài)方法或?qū)傩裕?

  • WebSocket.CONNECTING

  • WebSocket.OPEN

  • ...

因此,還需將它們拷貝到鉤子上。

框架頁消息

HTML5 賦予了框架頁跨域通信的能力。如果沒有為框架元素建立白名單的話,攻擊者可以嵌入自己的框架頁面,然后將 XSS 代碼 postMessage 給主頁面,通過 eval 執(zhí)行。

不過為了安全考慮,HTML5 在消息事件里保存了來源地址,以識別消息是哪個頁面發(fā)出的。

因為是個事件,我們可以使用第一篇文章里提到的方法,對其進行捕獲。每當(dāng)有消息收到時,可以根據(jù)策略,決定是否阻止該事件的傳遞。

        
          
            // 我們的防御系統(tǒng)
          
          
            (
          
          
            function
          
          
            ()
          
          
            {
          
          
            window
          
          
            .
          
          
            addEventListener
          
          
            (
          
          
            'message'
          
          
            ,
          
          
            function
          
          
            (
          
          
            e
          
          
            )
          
          
            {
          
          
            if
          
          
            (
          
          
            confirm
          
          
            (
          
          
            '發(fā)現(xiàn)來自['
          
          
            +
          
          
            e
          
          
            .
          
          
            origin
          
          
            +
          
          
            ']的消息:\n\n'
          
          
            +
          
          
            e
          
          
            .
          
          
            data
          
          
            +
          
          
            '\n\n是否攔截?'
          
          
            ))
          
          
            {
          
          
            e
          
          
            .
          
          
            stopImmediatePropagation
          
          
            ();
          
          
            }
          
          
            },
          
          
            true
          
          
            );
          
          
            })();
          
          
            window
          
          
            .
          
          
            addEventListener
          
          
            (
          
          
            'message'
          
          
            ,
          
          
            function
          
          
            (
          
          
            e
          
          
            )
          
          
            {
          
          
            alert
          
          
            (
          
          
            '收到:'
          
          
            +
          
          
            e
          
          
            .
          
          
            data
          
          
            )
          
          
            })
          
          
            postMessage
          
          
            (
          
          
            'hello'
          
          
            ,
          
          
            '*'
          
          
            );
          
        
      

Run

當(dāng)然,如果配置了框架頁的白名單,就能完全避免這回事了。所以這項防御可以選擇性的開啟。

事件源

HTML5 新增了一個叫 EventSource 的接口。不過其用法與 WebSocket 非常相似,因此可以使用類似的鉤子進行防御。

到此,我們列舉了各種能執(zhí)行遠程模塊的方式。事實上,對其防御并不難,難的是收集這些監(jiān)控點,做到滴水不漏。

API 鉤子

對于動態(tài)創(chuàng)建的可執(zhí)行模塊,我們通過屬性鉤子,來監(jiān)控其遠程路徑。

創(chuàng)建元素的方法

這一節(jié)是針對 Chrome 的,因為它不支持原生訪問器。

  • createElement / createElementNS 無中生有

  • cloneNode 克隆現(xiàn)有

  • innerHTML / outerHTML 工廠創(chuàng)建

前兩種,通過鉤子程序很容易實現(xiàn)。

第三種,因為 inner/outerHTML 是元素的 property,而非 attribute。由于 Chrome 是無法獲取原生訪問器的,所以使用鉤子會導(dǎo)致無法調(diào)用上級接口。

再者,inner/outerHTML 傳進來的是字符串。標簽和屬性魚龍混雜,解析字符串肯定是不靠譜的。所以還得先調(diào)用原生 innerHTML 批量構(gòu)建出節(jié)點,然后再掃描其中的元素。而這個過程中,節(jié)點掛載事件已經(jīng)觸發(fā)了。

所以,無需考慮第三種情況。

你可能會有疑問,既然用節(jié)點掛載事件都能搞定,為什么還要前面的鉤子?其實,在 第二篇文章 ?里已經(jīng)詳細討論了,動態(tài)創(chuàng)建的腳本沒法被事件攔截,所以才用鉤子。

而通過 innerHTML 產(chǎn)生的腳本,是不會執(zhí)行的!這個大家都聽說過吧。

修改屬性的訪問器

通過原型鏈的訪問器鉤子,可以直接監(jiān)控特定元素的特定 property,完全不影響他人,所以效率非常高。剛才列舉了可以執(zhí)行遠程模塊的元素,這些元素的路徑屬性,都得進行重寫訪問器。

當(dāng)然 Chrome 可以忽略這節(jié)。

修改屬性的方法

開頭也提到了,除了 setAttribute 外,使用 setAttributeNode 也能設(shè)置屬性,甚至還有 setAttributeNS 版本的。

由于 setAttribute 是個經(jīng)常調(diào)用的方法,因此鉤子程序必須做足夠的優(yōu)化,將額外的檢測消耗降到最低。

新頁面環(huán)境

除了使用最簡單的框架,其實還有其他可以獲得新頁面的途徑。

彈窗

通過彈窗也能獲得新頁面環(huán)境,大家都知道。但是窗口關(guān)閉,也隨之銷毀了,難道還能使用嗎?不妨測試一下:

        
          
            <style>
          
          
            .aa
          
          
            {
          
          
            color
          
          
            :
          
          
            red
          
          
            }
          
          
            </style>
          
          
            <button
          
          
            id=
          
          
            "btn"
          
          
            >
          
          POPUP
          
            </button>
          
          
            <script>
          
          
            btn
          
          
            .
          
          
            onclick
          
          
            =
          
          
            function
          
          
            ()
          
          
            {
          
          
            var
          
          
            win
          
          
            =
          
          
            window
          
          
            .
          
          
            open
          
          
            ();
          
          
            var
          
          
            raw_fn
          
          
            =
          
          
            win
          
          
            .
          
          
            Element
          
          
            .
          
          
            prototype
          
          
            .
          
          
            setAttribute
          
          
            ;
          
          
            win
          
          
            .
          
          
            close
          
          
            ();
          
          
            setTimeout
          
          
            (
          
          
            function
          
          
            ()
          
          
            {
          
          
            console
          
          
            .
          
          
            log
          
          
            (
          
          
            raw_fn
          
          
            );
          
          
            raw_fn
          
          
            .
          
          
            call
          
          
            (
          
          
            btn
          
          
            ,
          
          
            'class'
          
          
            ,
          
          
            'aa'
          
          
            );
          
          
            },
          
          
            1000
          
          
            );
          
          
            };
          
          
            </script>
          
        
      

Run

盡管會有瞬間的閃動,但從新窗口里獲取的變量確實被保留下來了,并且依然起作用。因為我們引用著它,所以即使窗口關(guān)閉,仍然不會對其內(nèi)存回收的。

現(xiàn)實中,可以把點擊事件綁在 document 上,這樣用戶隨便點哪里都能觸發(fā),以此獲得純凈的環(huán)境。

因此,我們還得把彈窗函數(shù),也通過鉤子保護起來。

除了最常用的 window.open,其實還有:

  • showModalDialog

  • showModelessDialog

opener

如果當(dāng)前網(wǎng)頁是從其他頁面點擊打開的,無論是彈窗還是超鏈接,window.opener 都記錄著來源頁的環(huán)境。

如果是來源頁和自己又是同源站點,甚至還能訪問到來源頁里面的變量。

這種情況相當(dāng)常見。例如從帖子列表頁,點開一個帖子詳情頁,那么詳情頁是完全可以操控列表頁的。

要解決這個問題也不難,直接給 window.opener 注入防護程序不就可以了,就像對待新出現(xiàn)的框架頁那樣。

但是,window.opener 可能也有自己的 opener,一層層遞歸上去或許有很多。每個頁面也許又有自己的框架頁,因此防護 window.opener 可能會執(zhí)行非常多的代碼。如果在初始化時就進行,或許會有性能問題。

事實上,這個冷門的屬性幾乎不怎么用到。所以不如做個延時策略:只有第一次訪問 opener 的時候,才對其進行防護。

我們將 window.opener 進行重寫,把它變成一個 getter 訪問器:

        
          
            var
          
          
            raw_opener
          
          
            =
          
          
            window
          
          
            .
          
          
            opener
          
          
            ;
          
          
            var
          
          
            scanned
          
          
            ;
          
          
            window
          
          
            .
          
          
            __defineGetter__
          
          
            (
          
          
            'opener'
          
          
            ,
          
          
            function
          
          
            ()
          
          
            {
          
          
            if
          
          
            (
          
          
            !
          
          
            scanned
          
          
            )
          
          
            {
          
          
            installHook
          
          
            (
          
          
            raw_opener
          
          
            );
          
          
            scanned
          
          
            =
          
          
            true
          
          
            ;
          
          
            }
          
          
            return
          
          
            raw_opener
          
          
            ;
          
          
            });
          
        
      

這樣,只要不訪問 opener,就不會觸發(fā)對它的防護,做到真正按需執(zhí)行。

后記

關(guān)于防護監(jiān)控點,也沒有一個完整的答案,能想到多少算多少,以后可以慢慢補充。

但是,裝了那么多的鉤子及事件,對頁面的性能影響有多大呢?

所以,我們還得開發(fā)一個測試控制臺,來跟蹤這套系統(tǒng)。看看監(jiān)控全開時,會對頁面產(chǎn)生多大影響。

XSS 前端防火墻 —— 天衣無縫的防護


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 爱爱爱av| 亚洲精品免费网站 | 天天草视频| 一区二区三区四区高清视频 | 亚洲午夜精品视频 | 亚洲啪啪 | 国产a做爰全过程片 | 香港全黄一级毛片在线播放 | 欧美高清观看免费全部完 | 99久久久久久| 国产精品91久久久久 | 日本粉嫩一区二区三区视频 | 妈妈的朋友酷客影响 | 小视频在线观看免费 | 免费中文字幕 | 国产在线精品一区二区高清不卡 | 2018天天干夜夜操 | 国产一区二区三区福利 | 国产成久久免费精品AV片天堂 | www91com国产91 | 久久久久无码国产精品一区 | 91看片免费版 | 色视频在线免费观看 | 欧美精选在线 | 亚洲精品视频一区 | 欧美特一级片 | 夜色4se.bar | 午夜午夜精品一区二区三区文 | 中文精品在线 | 老牛影视av一区二区在线观看 | 亚洲精品一区久久久久久 | 一级毛片视频免费 | 国产精品美女久久久久久免费 | 激情五月色综合国产精品 | 五月天激激婷婷大综合丁香 | 美女伊人 | 久久这里只有精品9 | 伊人亚洲精品 | 亚洲品质自拍视频 | 2020天天狠天天透天干天天怕 | 亚洲精品国产一区 |