JavaScript 防 http 劫持與 XSS
作為前端,一直以來(lái)都知道HTTP 劫持與XSS 跨站腳本(Cross-site scripting)、CSRF 跨站請(qǐng)求偽造(Cross-site request forgery)。但是一直都沒(méi)有深入
作為前端,一直以來(lái)都知道HTTP 劫持與XSS 跨站腳本(Cross-site scripting)、CSRF 跨站請(qǐng)求偽造(Cross-site request forgery)。但是一直都沒(méi)有深入研究過(guò),前些日子同事的分享會(huì)偶然提及,我也對(duì)這一塊很感興趣,便深入研究了一番。 最近用 JavaScript 寫(xiě)了一個(gè)組件,可以在前端層面防御部分 HTTP 劫持與 XSS 。 當(dāng)然,防御這些劫持最好的方法還是從后端入手,前端能做的實(shí)在太少。而且由于源碼的暴露,攻擊者很容易繞過(guò)我們的防御手段。但是這不代表我們?nèi)チ私膺@塊的相關(guān)知識(shí)是沒(méi)意義的,本文的許多方法,用在其他方面也是大有作用。
已上傳到 Github – httphijack.js ,歡迎感興趣看看順手點(diǎn)個(gè) star ,本文示例代碼,防范方法在組件源碼中皆可找到。
接下來(lái)進(jìn)入正文。
HTTP 劫持、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 劫持就更過(guò)分了,簡(jiǎn)單說(shuō)就是我們請(qǐng)求的是 直接被重定向了 ,本文不會(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è)面的影響。

這種情況還比較好處理,我們只需要知道我們的頁(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è)面:
JavaScript
1 if (self != top) {
2 // 我們的正常頁(yè)面
3 var url = location.href;
4 // 父級(jí)頁(yè)面重定向
5 top.location = url;
6 }
使用白名單放行正常 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 。
JavaScript
1 // 建立白名單
2 var whiteList = [
3
4 ];
5
6 if (self != top) {
7 var
8 // 使用 document.referrer 可以拿到跨域 iframe 父頁(yè)面的 URL
9 parentUrl = document.referrer,
10 length = whiteList.length,
,11 i = 0;
12
13 for(; i 14 // 建立白名單正則 3 // 此處需要建立一個(gè)白名單匹配規(guī)則,白名單默認(rèn)放行 4 if (self != top) { 5 var 6 // 使用 document.referrer 可以拿到跨域 iframe 父頁(yè)面的 URL 7 parentUrl = document.referrer, 8 length = whiteList.length, 9 i = 0; 10 11 for(; i 12 // 建立白名單正則 在 XSS 中,其實(shí)可以注入腳本的方式非常的多,尤其是 HTML5 出來(lái)之后,一不留神,許多的新標(biāo)簽都可以用于注入可執(zhí)行腳本。 2. 3. 4. 5. 除去一些未列出來(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 標(biāo)簽而言,真正觸發(fā)元素 alert(222) 是處于點(diǎn)擊事件的目標(biāo)階段。 點(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ě): JavaScript 1 // 建立關(guān)鍵詞黑名單 2 var keywordBlackList = [ 3 'xss', 4 'BAIDU_SSP__wrapper', 5 'BAIDU_DSPUI_FLOWBAR' 6 ]; 7 8 document.addEventListener('click', function(e) { 9 var code = ""; 10 11 // 掃描 的腳本 12 if (elem.tagName == 'A' && elem.protocol == 'javascript:') { 13 var code = elem.href.substr(11); 14 15 if (blackListMatch(keywordBlackList, code)) { 16 // 注銷(xiāo)代碼 17 elem.href = 'javascript:void(0)'; 18 console.log('攔截可疑事件:' code); 19 } 20 } 21 }, true); 22 23 /** 24 * [黑名單匹配] 25 * @param {[Array]} blackList [黑名單] 26 * @param {[String]} value [需要驗(yàn)證的字符串] 27 * @return {[Boolean]} [false -- 驗(yàn)證不通過(guò),true -- 驗(yàn)證通過(guò)] 28 */ 29 function blackListMatch(blackList, value) { 30 var length = blackList.length, 31 i = 0; 32 33 for (; i < length; i ) { 34 // 建立黑名單正則 XSS 跨站腳本的精髓不在于“跨站”,在于“腳本”。 通常而言,攻擊者或者運(yùn)營(yíng)商會(huì)向頁(yè)面中注入一個(gè)