亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解前端安全之JavaScript防http劫持與XSS

瀏覽:82日期:2023-06-03 09:27:34
目錄HTTP劫持、DNS劫持與XSSHTTP劫持DNS劫持XSS跨站腳本頁(yè)面被嵌入 iframe 中,重定向 iframe使用白名單放行正常 iframe 嵌套更改 URL 參數(shù)繞過(guò)運(yùn)營(yíng)商標(biāo)記內(nèi)聯(lián)事件及內(nèi)聯(lián)腳本攔截瀏覽器事件模型靜態(tài)腳本攔截使用白名單對(duì) src 進(jìn)行匹配過(guò)濾動(dòng)態(tài)腳本攔截Mutation Events 與 DOMNodeInserted重寫(xiě) setAttribute 與 document.write重寫(xiě)原生 Element.prototype.setAttribute 方法重寫(xiě)嵌套 iframe 內(nèi)的 Element.prototype.setAttribute重寫(xiě) document.write鎖死 apply 和 call建立攔截上報(bào)HTTPS 與 CSPCSPHTTPSHTTP劫持、DNS劫持與XSS

先簡(jiǎn)單講講什么是 HTTP 劫持與 DNS 劫持。

HTTP劫持

什么是HTTP劫持呢,大多數(shù)情況是運(yùn)營(yíng)商HTTP劫持,當(dāng)我們使用HTTP請(qǐng)求請(qǐng)求一個(gè)網(wǎng)站頁(yè)面的時(shí)候,網(wǎng)絡(luò)運(yùn)營(yíng)商會(huì)在正常的數(shù)據(jù)流中插入精心設(shè)計(jì)的網(wǎng)絡(luò)數(shù)據(jù)報(bào)文,讓客戶端(通常是瀏覽器)展示“錯(cuò)誤”的數(shù)據(jù),通常是一些彈窗,宣傳性廣告或者直接顯示某網(wǎng)站的內(nèi)容,大家應(yīng)該都有遇到過(guò)。

DNS劫持

DNS 劫持就是通過(guò)劫持了 DNS 服務(wù)器,通過(guò)某些手段取得某域名的解析記錄控制權(quán),進(jìn)而修改此域名的解析結(jié)果,導(dǎo)致對(duì)該域名的訪問(wèn)由原IP地址轉(zhuǎn)入到修改后的指定IP,其結(jié)果就是對(duì)特定的網(wǎng)址不能訪問(wèn)或訪問(wèn)的是假網(wǎng)址,從而實(shí)現(xiàn)竊取資料或者破壞原有正常服務(wù)的目的。

DNS 劫持比之 HTTP 劫持 更加過(guò)分,簡(jiǎn)單說(shuō)就是我們請(qǐng)求的是http://www.a.com/index.html ,直接被重定向了 http://www.b.com/index.html ,本文不會(huì)過(guò)多討論這種情況。

XSS跨站腳本

XSS指的是攻擊者利用漏洞,向 Web 頁(yè)面中注入惡意代碼,當(dāng)用戶瀏覽該頁(yè)之時(shí),注入的代碼會(huì)被執(zhí)行,從而達(dá)到攻擊的特殊目的。

關(guān)于這些攻擊如何生成,攻擊者如何注入惡意代碼到頁(yè)面中本文不做討論,只要知道如 HTTP 劫持 和 XSS 最終都是惡意代碼在客戶端,通常也就是用戶瀏覽器端執(zhí)行,本文將討論的就是假設(shè)注入已經(jīng)存在,如何利用 Javascript 進(jìn)行行之有效的前端防護(hù)。

頁(yè)面被嵌入 iframe 中,重定向 iframe

先來(lái)說(shuō)說(shuō)我們的頁(yè)面被嵌入了 iframe 的情況。也就是,網(wǎng)絡(luò)運(yùn)營(yíng)商為了盡可能地減少植入廣告對(duì)原有網(wǎng)站頁(yè)面的影響,通常會(huì)通過(guò)把原有網(wǎng)站頁(yè)面放置到一個(gè)和原頁(yè)面相同大小的 iframe 里面去,那么就可以通過(guò)這個(gè) iframe 來(lái)隔離廣告代碼對(duì)原有頁(yè)面的影響。

詳解前端安全之JavaScript防http劫持與XSS

這種情況還比較好處理,我們只需要知道我們的頁(yè)面是否被嵌套在 iframe 中,如果是,則重定向外層頁(yè)面到我們的正常頁(yè)面即可。

那么有沒(méi)有方法知道我們的頁(yè)面當(dāng)前存在于 iframe 中呢?有的,就是window.self與window.top。

window.self

返回一個(gè)指向當(dāng)前 window 對(duì)象的引用。

window.top

返回窗口體系中的最頂層窗口的引用。

對(duì)于非同源的域名,iframe 子頁(yè)面無(wú)法通過(guò) parent.location 或者 top.location 拿到具體的頁(yè)面地址,但是可以寫(xiě)入 top.location ,也就是可以控制父頁(yè)面的跳轉(zhuǎn)。

兩個(gè)屬性分別可以又簡(jiǎn)寫(xiě)為self與top,所以當(dāng)發(fā)現(xiàn)我們的頁(yè)面被嵌套在 iframe 時(shí),可以重定向父級(jí)頁(yè)面:

if (self != top) { // 我們的正常頁(yè)面 var url = location.href; // 父級(jí)頁(yè)面重定向 top.location = url;}使用白名單放行正常 iframe 嵌套

當(dāng)然很多時(shí)候,也許運(yùn)營(yíng)需要,我們的頁(yè)面會(huì)被以各種方式推廣,也有可能是正常業(yè)務(wù)需要被嵌套在 iframe 中,這個(gè)時(shí)候我們需要一個(gè)白名單或者黑名單,當(dāng)我們的頁(yè)面被嵌套在 iframe 中且父級(jí)頁(yè)面域名存在白名單中,則不做重定向操作。

上面也說(shuō)了,使用 top.location.href 是沒(méi)辦法拿到父級(jí)頁(yè)面的 URL 的,這時(shí)候,需要使用document.referrer。

通過(guò) document.referrer 可以拿到跨域 iframe 父頁(yè)面的URL。

// 建立白名單var whiteList = [ ’www.aaa.com’, ’res.bbb.com’]; if (self != top) { var // 使用 document.referrer 可以拿到跨域 iframe 父頁(yè)面的 URL parentUrl = document.referrer, length = whiteList.length, i = 0; for(; i<length; i++){ // 建立白名單正則 var reg = new RegExp(whiteList[i],’i’); // 存在白名單中,放行 if(reg.test(parentUrl)){ return; } } // 我們的正常頁(yè)面 var url = location.href; // 父級(jí)頁(yè)面重定向 top.location = url;}更改 URL 參數(shù)繞過(guò)運(yùn)營(yíng)商標(biāo)記

這樣就完了嗎?沒(méi)有,我們雖然重定向了父頁(yè)面,但是在重定向的過(guò)程中,既然第一次可以嵌套,那么這一次重定向的過(guò)程中頁(yè)面也許又被 iframe 嵌套了,真尼瑪?shù)疤邸?/p>

當(dāng)然運(yùn)營(yíng)商這種劫持通常也是有跡可循,最常規(guī)的手段是在頁(yè)面 URL 中設(shè)置一個(gè)參數(shù),例如 http://www.example.com/index.html?iframe_hijack_redirected=1,其中iframe_hijack_redirected=1表示頁(yè)面已經(jīng)被劫持過(guò)了,就不再嵌套 iframe 了。所以根據(jù)這個(gè)特性,我們可以改寫(xiě)我們的 URL ,使之看上去已經(jīng)被劫持了:

var flag = ’iframe_hijack_redirected’;// 當(dāng)前頁(yè)面存在于一個(gè) iframe 中// 此處需要建立一個(gè)白名單匹配規(guī)則,白名單默認(rèn)放行if (self != top) { var // 使用 document.referrer 可以拿到跨域 iframe 父頁(yè)面的 URL parentUrl = document.referrer, length = whiteList.length, i = 0; for(; i<length; i++){ // 建立白名單正則 var reg = new RegExp(whiteList[i],’i’); // 存在白名單中,放行 if(reg.test(parentUrl)){ return; } } var url = location.href; var parts = url.split(’#’); if (location.search) { parts[0] += ’&’ + flag + ’=1’; } else { parts[0] += ’?’ + flag + ’=1’; } try { console.log(’頁(yè)面被嵌入iframe中:’, url); top.location.href = parts.join(’#’); } catch (e) {}}

當(dāng)然,如果這個(gè)參數(shù)一改,防嵌套的代碼就失效了。所以我們還需要建立一個(gè)上報(bào)系統(tǒng),當(dāng)發(fā)現(xiàn)頁(yè)面被嵌套時(shí),發(fā)送一個(gè)攔截上報(bào),即便重定向失敗,也可以知道頁(yè)面嵌入 iframe 中的 URL,根據(jù)分析這些 URL ,不斷增強(qiáng)我們的防護(hù)手段,這個(gè)后文會(huì)提及。

內(nèi)聯(lián)事件及內(nèi)聯(lián)腳本攔截

在 XSS 中,其實(shí)可以注入腳本的方式非常的多,尤其是 HTML5 出來(lái)之后,一不留神,許多的新標(biāo)簽都可以用于注入可執(zhí)行腳本。

列出一些比較常見(jiàn)的注入方式:

1.<a href='javascript:alert(1)' rel='external nofollow' _fcksavedurl='javascript:alert(1)' ></a>

2.<iframe src='javascript:alert(1)' />

3.<img src=’x’ />

4.<video src=’x’ ></video>

5.<div onmouseover='alert(2)' ><div>

除去一些未列出來(lái)的非常少見(jiàn)生僻的注入方式,大部分都是javascript:...及內(nèi)聯(lián)事件on*。

我們假設(shè)注入已經(jīng)發(fā)生,那么有沒(méi)有辦法攔截這些內(nèi)聯(lián)事件與內(nèi)聯(lián)腳本的執(zhí)行呢?

對(duì)于上面列出的 (1) (5) ,這種需要用戶點(diǎn)擊或者執(zhí)行某種事件之后才執(zhí)行的腳本,我們是有辦法進(jìn)行防御的。

瀏覽器事件模型

這里說(shuō)能夠攔截,涉及到了事件模型相關(guān)的原理。

我們都知道,標(biāo)準(zhǔn)瀏覽器事件模型存在三個(gè)階段:

捕獲階段 目標(biāo)階段 冒泡階段

對(duì)于一個(gè)這樣<a href='javascript:alert(222)' rel='external nofollow' rel='external nofollow' _fcksavedurl='javascript:alert(222)' ></a>的 a 標(biāo)簽而言,真正觸發(fā)元素alert(222)是處于點(diǎn)擊事件的目標(biāo)階段。

<a href='javascript:alert(222)' rel='external nofollow' rel='external nofollow' >Click Me</a><script> document.addEventListener(’click’, function(e) { alert(111); }, true);</script>

點(diǎn)擊上面的click me,先彈出 111 ,后彈出 222。

那么,我們只需要在點(diǎn)擊事件模型的捕獲階段對(duì)標(biāo)簽內(nèi)javascript:...的內(nèi)容建立關(guān)鍵字黑名單,進(jìn)行過(guò)濾審查,就可以做到我們想要的攔截效果。

對(duì)于 on* 類(lèi)內(nèi)聯(lián)事件也是同理,只是對(duì)于這類(lèi)事件太多,我們沒(méi)辦法手動(dòng)枚舉,可以利用代碼自動(dòng)枚舉,完成對(duì)內(nèi)聯(lián)事件及內(nèi)聯(lián)腳本的攔截。

以攔截 a 標(biāo)簽內(nèi)的href='javascript:...為例,我們可以這樣寫(xiě):

// 建立關(guān)鍵詞黑名單var keywordBlackList = [ ’xss’, ’BAIDU_SSP__wrapper’, ’BAIDU_DSPUI_FLOWBAR’]; document.addEventListener(’click’, function(e) { var code = ''; // 掃描 <a href='javascript:' rel='external nofollow' > 的腳本 if (elem.tagName == ’A’ && elem.protocol == ’javascript:’) { var code = elem.href.substr(11); if (blackListMatch(keywordBlackList, code)) { // 注銷(xiāo)代碼 elem.href = ’javascript:void(0)’; console.log(’攔截可疑事件:’ + code); } }}, true); /** * [黑名單匹配] * @param {[Array]} blackList [黑名單] * @param {[String]} value [需要驗(yàn)證的字符串] * @return {[Boolean]} [false -- 驗(yàn)證不通過(guò),true -- 驗(yàn)證通過(guò)] */function blackListMatch(blackList, value) { var length = blackList.length, i = 0; for (; i < length; i++) { // 建立黑名單正則 var reg = new RegExp(whiteList[i], ’i’); // 存在黑名單中,攔截 if (reg.test(value)) { return true; } } return false;}

點(diǎn)擊圖中這幾個(gè)按鈕,可以看到如下:

詳解前端安全之JavaScript防http劫持與XSS

這里我們用到了黑名單匹配,下文還會(huì)細(xì)說(shuō)。

靜態(tài)腳本攔截

XSS 跨站腳本的精髓不在于“跨站”,在于“腳本”。

通常而言,攻擊者或者運(yùn)營(yíng)商會(huì)向頁(yè)面中注入一個(gè)<script>腳本,具體操作都在腳本中實(shí)現(xiàn),這種劫持方式只需要注入一次,有改動(dòng)的話不需要每次都重新注入。

我們假定現(xiàn)在頁(yè)面上被注入了一個(gè)<script src='http://attack.com/xss.js'>腳本,我們的目標(biāo)就是攔截這個(gè)腳本的執(zhí)行。

聽(tīng)起來(lái)很困難啊,什么意思呢。就是在腳本執(zhí)行前發(fā)現(xiàn)這個(gè)可疑腳本,并且銷(xiāo)毀它使之不能執(zhí)行內(nèi)部代碼。

所以我們需要用到一些高級(jí) API ,能夠在頁(yè)面加載時(shí)對(duì)生成的節(jié)點(diǎn)進(jìn)行檢測(cè)。

MutationObserver

MutationObserver 是 HTML5 新增的 API,功能很強(qiáng)大,給開(kāi)發(fā)者們提供了一種能在某個(gè)范圍內(nèi)的 DOM 樹(shù)發(fā)生變化時(shí)作出適當(dāng)反應(yīng)的能力。

說(shuō)的很玄乎,大概的意思就是能夠監(jiān)測(cè)到頁(yè)面 DOM 樹(shù)的變換,并作出反應(yīng)。

MutationObserver()該構(gòu)造函數(shù)用來(lái)實(shí)例化一個(gè)新的Mutation觀察者對(duì)象。

MutationObserver( function callback);

目瞪狗呆,這一大段又是啥?意思就是 MutationObserver 在觀測(cè)時(shí)并非發(fā)現(xiàn)一個(gè)新元素就立即回調(diào),而是將一個(gè)時(shí)間片段里出現(xiàn)的所有元素,一起傳過(guò)來(lái)。所以在回調(diào)中我們需要進(jìn)行批量處理。而且,其中的callback會(huì)在指定的 DOM 節(jié)點(diǎn)(目標(biāo)節(jié)點(diǎn))發(fā)生變化時(shí)被調(diào)用。在調(diào)用時(shí),觀察者對(duì)象會(huì)傳給該函數(shù)兩個(gè)參數(shù),第一個(gè)參數(shù)是個(gè)包含了若干個(gè) MutationRecord 對(duì)象的數(shù)組,第二個(gè)參數(shù)則是這個(gè)觀察者對(duì)象本身。

所以,使用 MutationObserver ,我們可以對(duì)頁(yè)面加載的每個(gè)靜態(tài)腳本文件,進(jìn)行監(jiān)控:

// MutationObserver 的不同兼容性寫(xiě)法var MutationObserver = window.MutationObserver || window.WebKitMutationObserver ||window.MozMutationObserver;// 該構(gòu)造函數(shù)用來(lái)實(shí)例化一個(gè)新的 Mutation 觀察者對(duì)象// Mutation 觀察者對(duì)象能監(jiān)聽(tīng)在某個(gè)范圍內(nèi)的 DOM 樹(shù)變化var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // 返回被添加的節(jié)點(diǎn),或者為null. var nodes = mutation.addedNodes; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (/xss/i.test(node.src))) {try { node.parentNode.removeChild(node); console.log(’攔截可疑靜態(tài)腳本:’, node.src);} catch (e) {} } } });}); // 傳入目標(biāo)節(jié)點(diǎn)和觀察選項(xiàng)// 如果 target 為 document 或者 document.documentElement// 則當(dāng)前文檔中所有的節(jié)點(diǎn)添加與刪除操作都會(huì)被觀察到observer.observe(document, { subtree: true, childList: true});

詳解前端安全之JavaScript防http劫持與XSS

<script type='text/javascript' src='http://www.aoyou183.cn/bcjs/xss/a.js'></script>是頁(yè)面加載一開(kāi)始就存在的靜態(tài)腳本(查看頁(yè)面結(jié)構(gòu)),我們使用 MutationObserver 可以在腳本加載之后,執(zhí)行之前這個(gè)時(shí)間段對(duì)其內(nèi)容做正則匹配,發(fā)現(xiàn)惡意代碼則removeChild()掉,使之無(wú)法執(zhí)行。

使用白名單對(duì) src 進(jìn)行匹配過(guò)濾

上面的代碼中,我們判斷一個(gè)js腳本是否是惡意的,用的是這一句:

if (/xss/i.test(node.src)) {}

當(dāng)然實(shí)際當(dāng)中,注入惡意代碼者不會(huì)那么傻,把名字改成 XSS 。所以,我們很有必要使用白名單進(jìn)行過(guò)濾和建立一個(gè)攔截上報(bào)系統(tǒng)。

// 建立白名單var whiteList = [ ’www.aaa.com’, ’res.bbb.com’]; /** * [白名單匹配] * @param {[Array]} whileList [白名單] * @param {[String]} value [需要驗(yàn)證的字符串] * @return {[Boolean]} [false -- 驗(yàn)證不通過(guò),true -- 驗(yàn)證通過(guò)] */function whileListMatch(whileList, value) { var length = whileList.length, i = 0; for (; i < length; i++) { // 建立白名單正則 var reg = new RegExp(whiteList[i], ’i’); // 存在白名單中,放行 if (reg.test(value)) { return true; } } return false;} // 只放行白名單if (!whileListMatch(blackList, node.src)) { node.parentNode.removeChild(node);} 

這里我們已經(jīng)多次提到白名單匹配了,下文還會(huì)用到,所以可以這里把它簡(jiǎn)單封裝成一個(gè)方法調(diào)用。

動(dòng)態(tài)腳本攔截

上面使用 MutationObserver 攔截靜態(tài)腳本,除了靜態(tài)腳本,與之對(duì)應(yīng)的就是動(dòng)態(tài)生成的腳本。

var script = document.createElement(’script’);script.type = ’text/javascript’;script.src = ’http://www.example.com/xss/b.js’; document.getElementsByTagName(’body’)[0].appendChild(script); 

要攔截這類(lèi)動(dòng)態(tài)生成的腳本,且攔截時(shí)機(jī)要在它插入 DOM 樹(shù)中,執(zhí)行之前,本來(lái)是可以監(jiān)聽(tīng)Mutation Events中的DOMNodeInserted事件的。

Mutation Events 與 DOMNodeInserted

打開(kāi)MDN,第一句就是:

該特性已經(jīng)從 Web 標(biāo)準(zhǔn)中刪除,雖然一些瀏覽器目前仍然支持它,但也許會(huì)在未來(lái)的某個(gè)時(shí)間停止支持,請(qǐng)盡量不要使用該特性。

雖然不能用,也可以了解一下:

document.addEventListener(’DOMNodeInserted’, function(e) { var node = e.target; if (/xss/i.test(node.src) || /xss/i.test(node.innerHTML)) { node.parentNode.removeChild(node); console.log(’攔截可疑動(dòng)態(tài)腳本:’, node); }}, true);

然而可惜的是,使用上面的代碼攔截動(dòng)態(tài)生成的腳本,可以攔截到,但是代碼也執(zhí)行了:DOMNodeInserted顧名思義,可以監(jiān)聽(tīng)某個(gè) DOM 范圍內(nèi)的結(jié)構(gòu)變化,與MutationObserver相比,它的執(zhí)行時(shí)機(jī)更早。

詳解前端安全之JavaScript防http劫持與XSS

但是DOMNodeInserted不再建議使用,所以監(jiān)聽(tīng)動(dòng)態(tài)腳本的任務(wù)也要交給MutationObserver。

可惜的是,在實(shí)際實(shí)踐過(guò)程中,使用MutationObserver的結(jié)果和DOMNodeInserted一樣,可以監(jiān)聽(tīng)攔截到動(dòng)態(tài)腳本的生成,但是無(wú)法在腳本執(zhí)行之前,使用removeChild將其移除,所以我們還需要想想其他辦法。

重寫(xiě) setAttribute 與 document.write重寫(xiě)原生 Element.prototype.setAttribute 方法

在動(dòng)態(tài)腳本插入執(zhí)行前,監(jiān)聽(tīng) DOM 樹(shù)的變化攔截它行不通,腳本仍然會(huì)執(zhí)行。

那么我們需要向上尋找,在腳本插入 DOM 樹(shù)前的捕獲它,那就是創(chuàng)建腳本時(shí)這個(gè)時(shí)機(jī)。

假設(shè)現(xiàn)在有一個(gè)動(dòng)態(tài)腳本是這樣創(chuàng)建的:

var script = document.createElement(’script’);script.setAttribute(’type’, ’text/javascript’);script.setAttribute(’src’, ’http://www.example.com/xss/c.js’); document.getElementsByTagName(’body’)[0].appendChild(script);

而重寫(xiě)Element.prototype.setAttribute也是可行的:我們發(fā)現(xiàn)這里用到了 setAttribute 方法,如果我們能夠改寫(xiě)這個(gè)原生方法,監(jiān)聽(tīng)設(shè)置src屬性時(shí)的值,通過(guò)黑名單或者白名單判斷它,就可以判斷該標(biāo)簽的合法性了。

// 保存原有接口var old_setAttribute = Element.prototype.setAttribute; // 重寫(xiě) setAttribute 接口Element.prototype.setAttribute = function(name, value) { // 匹配到 <script src=’xxx’ > 類(lèi)型 if (this.tagName == ’SCRIPT’ && /^src$/i.test(name)) { // 白名單匹配 if (!whileListMatch(whiteList, value)) { console.log(’攔截可疑模塊:’, value); return; } } // 調(diào)用原始接口 old_setAttribute.apply(this, arguments);}; // 建立白名單var whiteList = [’www.yy.com’,’res.cont.yy.com’]; /** * [白名單匹配] * @param {[Array]} whileList [白名單] * @param {[String]} value [需要驗(yàn)證的字符串] * @return {[Boolean]} [false -- 驗(yàn)證不通過(guò),true -- 驗(yàn)證通過(guò)] */function whileListMatch(whileList, value) { var length = whileList.length, i = 0; for (; i < length; i++) { // 建立白名單正則 var reg = new RegExp(whiteList[i], ’i’); // 存在白名單中,放行 if (reg.test(value)) { return true; } } return false;}

詳解前端安全之JavaScript防http劫持與XSS

重寫(xiě)Element.prototype.setAttribute,就是首先保存原有接口,然后當(dāng)有元素調(diào)用 setAttribute 時(shí),檢查傳入的 src 是否存在于白名單中,存在則放行,不存在則視為可疑元素,進(jìn)行上報(bào)并不予以執(zhí)行。最后對(duì)放行的元素執(zhí)行原生的setAttribute,也就是old_setAttribute.apply(this, arguments);。

上述的白名單匹配也可以換成黑名單匹配。

重寫(xiě)嵌套 iframe 內(nèi)的 Element.prototype.setAttribute

當(dāng)然,上面的寫(xiě)法如果old_setAttribute = Element.prototype.setAttribute暴露給攻擊者的話,直接使用old_setAttribute就可以繞過(guò)我們重寫(xiě)的方法了,所以這段代碼必須包在一個(gè)閉包內(nèi)。

當(dāng)然這樣也不保險(xiǎn),雖然當(dāng)前窗口下的Element.prototype.setAttribute已經(jīng)被重寫(xiě)了。但是還是有手段可以拿到原生的Element.prototype.setAttribute,只需要一個(gè)新的 iframe 。

var newIframe = document.createElement(’iframe’);document.body.appendChild(newIframe); Element.prototype.setAttribute = newIframe.contentWindow.Element.prototype.setAttribute;

通過(guò)這個(gè)方法,可以重新拿到原生的Element.prototype.setAttribute,因?yàn)?iframe 內(nèi)的環(huán)境和外層 window 是完全隔離的。wtf?

怎么辦?我們看到創(chuàng)建 iframe 用到了createElement,那么是否可以重寫(xiě)原生createElement呢?但是除了createElement還有createElementNS,還有可能是頁(yè)面上已經(jīng)存在 iframe,所以不合適。

那就在每當(dāng)新創(chuàng)建一個(gè)新 iframe 時(shí),對(duì)setAttribute進(jìn)行保護(hù)重寫(xiě),這里又有用到MutationObserver:

/** * 使用 MutationObserver 對(duì)生成的 iframe 頁(yè)面進(jìn)行監(jiān)控, * 防止調(diào)用內(nèi)部原生 setAttribute 及 document.write * @return {[type]} [description] */function defenseIframe() { // 先保護(hù)當(dāng)前頁(yè)面 installHook(window);} /** * 實(shí)現(xiàn)單個(gè) window 窗口的 setAttribute保護(hù) * @param {[BOM]} window [瀏覽器window對(duì)象] * @return {[type]} [description] */function installHook(window) { // 重寫(xiě)單個(gè) window 窗口的 setAttribute 屬性 resetSetAttribute(window); // MutationObserver 的不同兼容性寫(xiě)法 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; // 該構(gòu)造函數(shù)用來(lái)實(shí)例化一個(gè)新的 Mutation 觀察者對(duì)象 // Mutation 觀察者對(duì)象能監(jiān)聽(tīng)在某個(gè)范圍內(nèi)的 DOM 樹(shù)變化 var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { // 返回被添加的節(jié)點(diǎn),或者為null. var nodes = mutation.addedNodes; // 逐個(gè)遍歷 for (var i = 0; i < nodes.length; i++) {var node = nodes[i]; // 給生成的 iframe 里環(huán)境也裝上重寫(xiě)的鉤子if (node.tagName == ’IFRAME’) { installHook(node.contentWindow);} } }); }); observer.observe(document, { subtree: true, childList: true });} /** * 重寫(xiě)單個(gè) window 窗口的 setAttribute 屬性 * @param {[BOM]} window [瀏覽器window對(duì)象] * @return {[type]} [description] */function resetSetAttribute(window) { // 保存原有接口 var old_setAttribute = window.Element.prototype.setAttribute; // 重寫(xiě) setAttribute 接口 window.Element.prototype.setAttribute = function(name, value) { ... };} 

我們定義了一個(gè)installHook方法,參數(shù)是一個(gè)window,在這個(gè)方法里,我們將重寫(xiě)傳入的window下的 setAttribute ,并且安裝一個(gè)MutationObserver,并對(duì)此窗口下未來(lái)可能創(chuàng)建的iframe進(jìn)行監(jiān)聽(tīng),如果未來(lái)在此window下創(chuàng)建了一個(gè) iframe ,則對(duì)新的iframe也裝上installHook方法,以此進(jìn)行層層保護(hù)。

重寫(xiě) document.write

根據(jù)上述的方法,我們可以繼續(xù)挖掘一下,還有什么方法可以重寫(xiě),以便對(duì)頁(yè)面進(jìn)行更好的保護(hù)。

document.write是一個(gè)很不錯(cuò)選擇,注入攻擊者,通常會(huì)使用這個(gè)方法,往頁(yè)面上注入一些彈窗廣告。

我們可以重寫(xiě)document.write,使用關(guān)鍵詞黑名單對(duì)內(nèi)容進(jìn)行匹配。

什么比較適合當(dāng)黑名單的關(guān)鍵字呢?我們可以看看一些廣告很多的頁(yè)面:

詳解前端安全之JavaScript防http劫持與XSS

這里在頁(yè)面最底部嵌入了一個(gè) iframe ,里面裝了廣告代碼,這里的最外層的 id 名id='BAIDU_SSP__wrapper_u2444091_0'就很適合成為我們判斷是否是惡意代碼的一個(gè)標(biāo)志,假設(shè)我們已經(jīng)根據(jù)攔截上報(bào)收集到了一批黑名單列表:

// 建立正則攔截關(guān)鍵詞var keywordBlackList = [’xss’,’BAIDU_SSP__wrapper’,’BAIDU_DSPUI_FLOWBAR’];

接下來(lái)我們只需要利用這些關(guān)鍵字,對(duì)document.write傳入的內(nèi)容進(jìn)行正則判斷,就能確定是否要攔截document.write這段代碼。 

// 建立關(guān)鍵詞黑名單var keywordBlackList = [ ’xss’, ’BAIDU_SSP__wrapper’, ’BAIDU_DSPUI_FLOWBAR’]; /** * 重寫(xiě)單個(gè) window 窗口的 document.write 屬性 * @param {[BOM]} window [瀏覽器window對(duì)象] * @return {[type]} [description] */function resetDocumentWrite(window) { var old_write = window.document.write; window.document.write = function(string) { if (blackListMatch(keywordBlackList, string)) { console.log(’攔截可疑模塊:’, string); return; } // 調(diào)用原始接口 old_write.apply(document, arguments); }} /** * [黑名單匹配] * @param {[Array]} blackList [黑名單] * @param {[String]} value [需要驗(yàn)證的字符串] * @return {[Boolean]} [false -- 驗(yàn)證不通過(guò),true -- 驗(yàn)證通過(guò)] */function blackListMatch(blackList, value) { var length = blackList.length, i = 0; for (; i < length; i++) { // 建立黑名單正則 var reg = new RegExp(whiteList[i], ’i’); // 存在黑名單中,攔截 if (reg.test(value)) { return true; } } return false;}

我們可以把resetDocumentWrite放入上文的installHook方法中,就能對(duì)當(dāng)前 window 及所有生成的 iframe 環(huán)境內(nèi)的document.write進(jìn)行重寫(xiě)了。

鎖死 apply 和 call

接下來(lái)要介紹的這個(gè)是鎖住原生的 Function.prototype.apply 和 Function.prototype.call 方法,鎖住的意思就是使之無(wú)法被重寫(xiě)。

這里要用到Object.defineProperty,用于鎖死 apply 和 call。

Object.defineProperty

Object.defineProperty() 方法直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)已經(jīng)存在的屬性, 并返回這個(gè)對(duì)象。

Object.defineProperty(obj, prop, descriptor)

其中:

obj ? 需要定義屬性的對(duì)象 prop ? 需被定義或修改的屬性名 descriptor ? 需被定義或修改的屬性的描述符

我們可以使用如下的代碼,讓 call 和 apply 無(wú)法被重寫(xiě)。

// 鎖住 callObject.defineProperty(Function.prototype, ’call’, { value: Function.prototype.call, // 當(dāng)且僅當(dāng)僅當(dāng)該屬性的 writable 為 true 時(shí),該屬性才能被賦值運(yùn)算符改變 writable: false, // 當(dāng)且僅當(dāng)該屬性的 configurable 為 true 時(shí),該屬性才能夠被改變,也能夠被刪除 configurable: false, enumerable: true});// 鎖住 applyObject.defineProperty(Function.prototype, ’apply’, { value: Function.prototype.apply, writable: false, configurable: false, enumerable: true}); 

為啥要這樣寫(xiě)呢?其實(shí)還是與上文的重寫(xiě) setAttribute有關(guān)。

雖然我們將原始 Element.prototype.setAttribute 保存在了一個(gè)閉包當(dāng)中,但是還有奇技淫巧可以把它從閉包中給“偷出來(lái)”。

試一下:

(function() {})( // 保存原有接口 var old_setAttribute = Element.prototype.setAttribute; // 重寫(xiě) setAttribute 接口 Element.prototype.setAttribute = function(name, value) {// 具體細(xì)節(jié)if (this.tagName == ’SCRIPT’ && /^src$/i.test(name)) {}// 調(diào)用原始接口old_setAttribute.apply(this, arguments); };)();// 重寫(xiě) applyFunction.prototype.apply = function(){ console.log(this);}// 調(diào)用 setAttributedocument.getElementsByTagName(’body’)[0].setAttribute(’data-test’,’123’); 

猜猜上面一段會(huì)輸出什么?看看:

詳解前端安全之JavaScript防http劫持與XSS

居然返回了原生 setAttribute 方法!

這是因?yàn)槲覀冊(cè)谥貙?xiě)Element.prototype.setAttribute時(shí)最后有old_setAttribute.apply(this, arguments);這一句,使用到了 apply 方法,所以我們?cè)僦貙?xiě)apply,輸出this,當(dāng)調(diào)用被重寫(xiě)后的 setAttribute 就可以從中反向拿到原生的被保存起來(lái)的old_setAttribute了。

這樣我們上面所做的嵌套 iframe 重寫(xiě) setAttribute 就毫無(wú)意義了。

使用上面的Object.defineProperty可以鎖死 apply 和 類(lèi)似用法的 call 。使之無(wú)法被重寫(xiě),那么也就無(wú)法從閉包中將我們的原生接口偷出來(lái)。這個(gè)時(shí)候才算真正意義上的成功重寫(xiě)了我們想重寫(xiě)的屬性。

建立攔截上報(bào)

防御的手段也有一些了,接下來(lái)我們要建立一個(gè)上報(bào)系統(tǒng),替換上文中的 console.log() 日志。

上報(bào)系統(tǒng)有什么用呢?因?yàn)槲覀冇玫搅税酌麊危P(guān)鍵字黑名單,這些數(shù)據(jù)都需要不斷的豐富,靠的就是上報(bào)系統(tǒng),將每次攔截的信息傳到服務(wù)器,不僅可以讓我們程序員第一時(shí)間得知攻擊的發(fā)生,更可以讓我們不斷收集這類(lèi)相關(guān)信息以便更好的應(yīng)對(duì)。

這里的示例我用nodejs搭一個(gè)十分簡(jiǎn)易的服務(wù)器接受 http 上報(bào)請(qǐng)求。

先定義一個(gè)上報(bào)函數(shù):

/** * 自定義上報(bào) -- 替換頁(yè)面中的 console.log() * @param {[String]} name [攔截類(lèi)型] * @param {[String]} value [攔截值] */function hijackReport(name, value) { var img = document.createElement(’img’), hijackName = name, hijackValue = value.toString(), curDate = new Date().getTime(); // 上報(bào) img.src = ’http://www.reportServer.com/report/?msg=’ + hijackName + ’&value=’ + hijackValue + ’&time=’ + curDate;}

假定我們的服務(wù)器地址是www.reportServer.com這里,我們運(yùn)用img.src發(fā)送一個(gè) http 請(qǐng)求到服務(wù)器http://www.reportServer.com/report/,每次會(huì)帶上我們自定義的攔截類(lèi)型,攔截內(nèi)容以及上報(bào)時(shí)間。

用 Express 搭 nodejs 服務(wù)器并寫(xiě)一個(gè)簡(jiǎn)單的接收路由:

var express = require(’express’);var app = express(); app.get(’/report/’, function(req, res) { var queryMsg = req.query.msg,queryValue = req.query.value,queryTime = new Date(parseInt(req.query.time)); if (queryMsg) {console.log(’攔截類(lèi)型:’ + queryMsg); } if (queryValue) {console.log(’攔截值:’ + queryValue); } if (queryTime) {console.log(’攔截時(shí)間:’ + req.query.time); }}); app.listen(3002, function() { console.log(’HttpHijack Server listening on port 3002!’);});

運(yùn)行服務(wù)器,當(dāng)有上報(bào)發(fā)生,我們將會(huì)接收到如下數(shù)據(jù):

詳解前端安全之JavaScript防http劫持與XSS

好接下來(lái)就是數(shù)據(jù)入庫(kù),分析,添加黑名單,使用nodejs當(dāng)然攔截發(fā)生時(shí)發(fā)送郵件通知程序員等等,這些就不再做展開(kāi)。

HTTPS 與 CSP

最后再簡(jiǎn)單談?wù)?HTTPS 與 CSP。其實(shí)防御劫持最好的方法還是從后端入手,前端能做的實(shí)在太少。而且由于源碼的暴露,攻擊者很容易繞過(guò)我們的防御手段。

CSP

CSP 即是 Content Security Policy,翻譯為內(nèi)容安全策略。這個(gè)規(guī)范與內(nèi)容安全有關(guān),主要是用來(lái)定義頁(yè)面可以加載哪些資源,減少 XSS 的發(fā)生。

MDN ?CSP

HTTPS

能夠?qū)嵤?HTTP 劫持的根本原因,是 HTTP 協(xié)議沒(méi)有辦法對(duì)通信對(duì)方的身份進(jìn)行校驗(yàn)以及對(duì)數(shù)據(jù)完整性進(jìn)行校驗(yàn)。如果能解決這個(gè)問(wèn)題,則劫持將無(wú)法輕易發(fā)生。

HTTPS,是 HTTP over SSL 的意思。SSL 協(xié)議是 Netscape 在 1995 年首次提出的用于解決傳輸層安全問(wèn)題的網(wǎng)絡(luò)協(xié)議,其核心是基于公鑰密碼學(xué)理論實(shí)現(xiàn)了對(duì)服務(wù)器身份認(rèn)證、數(shù)據(jù)的私密性保護(hù)以及對(duì)數(shù)據(jù)完整性的校驗(yàn)等功能。

因?yàn)榕c本文主要內(nèi)容關(guān)聯(lián)性不大,關(guān)于更多 CSP 和 HTTPS 的內(nèi)容可以自行谷歌。

以上就是詳解前端安全之JavaScript防http劫持與XSS的詳細(xì)內(nèi)容,更多關(guān)于前端安全之JavaScript防http劫持與XSS的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: JavaScript
相關(guān)文章:
主站蜘蛛池模板: 99久久国产综合精品成人影院 | 香蕉香蕉国产片一级一级毛片 | 国产成人高清精品免费观看 | 亚洲人交性视频 | 亚洲国产一级毛片 | 亚洲欧洲中文日产 | 午夜成年女人毛片免费观看 | 亚洲视频二区 | 亚洲精品国产乱码在线播 | 中文字幕日韩高清 | 国产剧情网站 | 日本免费大黄在线观看 | 亚洲综合精品成人 | 亚洲欧美在线一区二区 | 97视频免费在线 | 精品国产高清不卡毛片 | 日韩亚洲成a人片在线观看 日韩亚洲欧美一区二区三区 | 国产馆精品推荐在线观看 | 久久日本精品久久久久久 | 国产一区视频在线 | 国产精品视频第一区二区三区 | 国产超级碰碰在线公开视频 | 欧美在线黄色片 | 国产毛片久久国产 | 国产亚洲欧洲国产综合一区 | 欧美综合在线观看 | 漂亮大学生一级毛片 | 欧美精品一区二区三区视频 | 中国一级特黄真人毛片免 | 日韩免费中文字幕 | 国产日韩欧美中文 | 99欧美精品 | 日韩经典欧美一区二区三区 | 精品网址 | 中文字幕日韩精品中文区 | 日韩第一页在线 | 成人免费在线网站 | 国产午夜成人无码免费看 | 国模精品一区二区三区 | 国产一级黄色片子 | 欧美日韩大片 |