JavaScript 防 http 劫持與 XSS
作為前端,一直以來都知道HTTP 劫持與XSS 跨站腳本(Cross-site scripting)、CSRF 跨站請求偽造(Cross-site request forgery)。但是一直都沒有深入
作為前端,一直以來都知道HTTP 劫持與XSS 跨站腳本(Cross-site scripting)、CSRF 跨站請求偽造(Cross-site request forgery)。但是一直都沒有深入研究過,前些日子同事的分享會偶然提及,我也對這一塊很感興趣,便深入研究了一番。 最近用 JavaScript 寫了一個組件,可以在前端層面防御部分 HTTP 劫持與 XSS 。 當(dāng)然,防御這些劫持最好的方法還是從后端入手,前端能做的實在太少。而且由于源碼的暴露,攻擊者很容易繞過我們的防御手段。但是這不代表我們?nèi)チ私膺@塊的相關(guān)知識是沒意義的,本文的許多方法,用在其他方面也是大有作用。
已上傳到 Github – httphijack.js ,歡迎感興趣看看順手點個 star ,本文示例代碼,防范方法在組件源碼中皆可找到。
接下來進入正文。
HTTP 劫持、DNS 劫持與XSS
先簡單講講什么是 HTTP 劫持與 DNS 劫持。
HTTP 劫持
什么是HTTP 劫持呢,大多數(shù)情況是運營商HTTP 劫持,當(dāng)我們使用HTTP 請求請求一個網(wǎng)站頁面的時候,網(wǎng)絡(luò)運營商會在正常的數(shù)據(jù)流中插入精心設(shè)計的網(wǎng)絡(luò)數(shù)據(jù)報文,讓客戶端(通常是瀏覽器)展示“錯誤”的數(shù)據(jù),通常是一些彈窗,宣傳性廣告或者直接顯示某網(wǎng)站的內(nèi)容,大家應(yīng)該都有遇到過。
DNS 劫持
,DNS 劫持就是通過劫持了DNS 服務(wù)器,通過某些手段取得某域名的解析記錄控制權(quán),進而修改此域名的解析結(jié)果,導(dǎo)致對該域名的訪問由原IP 地址轉(zhuǎn)入到修改后的指定IP ,其結(jié)果就是對特定的網(wǎng)址不能訪問或訪問的是假網(wǎng)址,從而實現(xiàn)竊取資料或者破壞原有正常服務(wù)的目的。
DNS 劫持就更過分了,簡單說就是我們請求的是 直接被重定向了 ,本文不會過多討論這種情況。
XSS 跨站腳本
XSS 指的是攻擊者漏洞,向 Web 頁面中注入惡意代碼,當(dāng)用戶瀏覽該頁之時,注入的代碼會被執(zhí)行,從而達(dá)到攻擊的特殊目的。
關(guān)于這些攻擊如何生成,攻擊者如何注入惡意代碼到頁面中本文不做討論,只要知道如 HTTP 劫持 和 XSS 最終都是惡意代碼在客戶端,通常也就是用戶瀏覽器端執(zhí)行,本文將討論的就是假設(shè)注入已經(jīng)存在,如何利用 Javascript 進行行之有效的前端防護。
頁面被嵌入 iframe 中,重定向 iframe
先來說說我們的頁面被嵌入了 iframe 的情況。也就是,網(wǎng)絡(luò)運營商為了盡可能地減少植入廣告對原有網(wǎng)站頁面的影響,通常會通過把原有網(wǎng)站頁面放置到一個和原頁面相同大小的 iframe 里面去,那么就可以通過這個 iframe 來隔離廣告代碼對
,原有頁面的影響。

這種情況還比較好處理,我們只需要知道我們的頁面是否被嵌套在 iframe 中,如果是,則重定向外層頁面到我們的正常頁面即可。
那么有沒有方法知道我們的頁面當(dāng)前存在于 iframe 中呢?有的,就
是 window.self 與 window.top 。
window.self
返回一個指向當(dāng)前 window 對象的引用。
window.top
返回窗口體系中的最頂層窗口的引用。
對于非同源的域名,iframe 子頁面無法通過 parent.location 或者 top.location 拿到具體的頁面地址,但是可以寫入 top.location ,也就是可以控制父頁面的跳轉(zhuǎn)。
,兩個屬性分別可以又簡寫為 self 與 top ,所以當(dāng)發(fā)現(xiàn)我們的頁面被嵌套在 iframe 時,可以重定向父級頁面:
JavaScript
1 if (self != top) {
2 // 我們的正常頁面
3 var url = location.href;
4 // 父級頁面重定向
5 top.location = url;
6 }
使用白名單放行正常 iframe 嵌套
當(dāng)然很多時候,也許運營需要,我們的頁面會被以各種方式推廣,也有可能是正常業(yè)務(wù)需要被嵌套在 iframe 中,這個時候我們需要一個白名單或者黑名單,當(dāng)我們的頁面被嵌套在 iframe 中且父級頁面域名存在白名單中,則不做重定向操作。 上面也說了,使用 top.location.href 是沒辦法拿到父級頁面的 URL 的,這時候,需要使用document.referrer 。
通過 document.referrer 可以拿到跨域 iframe 父頁面的URL 。
JavaScript
1 // 建立白名單
2 var whiteList = [
3
4 ];
5
6 if (self != top) {
7 var
8 // 使用 document.referrer 可以拿到跨域 iframe 父頁面的 URL
9 parentUrl = document.referrer,
10 length = whiteList.length,
,11 i = 0;
12
13 for(; i 14 // 建立白名單正則 3 // 此處需要建立一個白名單匹配規(guī)則,白名單默認(rèn)放行 4 if (self != top) { 5 var 6 // 使用 document.referrer 可以拿到跨域 iframe 父頁面的 URL 7 parentUrl = document.referrer, 8 length = whiteList.length, 9 i = 0; 10 11 for(; i 12 // 建立白名單正則 在 XSS 中,其實可以注入腳本的方式非常的多,尤其是 HTML5 出來之后,一不留神,許多的新標(biāo)簽都可以用于注入可執(zhí)行腳本。 2. 3. 4. 5. 除去一些未列出來的非常少見生僻的注入方式,大部分都是 javascript:... 及內(nèi)聯(lián)事件 on*。 我們假設(shè)注入已經(jīng)發(fā)生,那么有沒有辦法攔截這些內(nèi)聯(lián)事件與內(nèi)聯(lián)腳本的執(zhí)行呢? 對于上面列出的 (1) (5) ,這種需要用戶點擊或者執(zhí)行某種事件之后才執(zhí)行的腳本,我們是有辦法進行防御的。 瀏覽器事件模型 這里說能夠攔截,涉及到了事件模型相關(guān)的原理。 我們都知道,標(biāo)準(zhǔn)瀏覽器事件模型存在三個階段: ? ? ? 捕獲階段 目標(biāo)階段 冒泡階段 對于一個這樣 的 a 標(biāo)簽而言,真正觸發(fā)元素 alert(222) 是處于點擊事件的目標(biāo)階段。 點擊上面的 click me ,先彈出 111 ,后彈出 222。 那么,我們只需要在點擊事件模型的捕獲階段對標(biāo)簽內(nèi) javascript:... 的內(nèi)容建立關(guān)鍵字黑名單,進行過濾審查,就可以做到我們想要的攔截效果。 對于 on* 類內(nèi)聯(lián)事件也是同理,只是對于這類事件太多,我們沒辦法手動枚舉,可以利用代碼自動枚舉,完成對內(nèi)聯(lián)事件及內(nèi)聯(lián)腳本的攔截。 以攔截 a 標(biāo)簽內(nèi)的 href="javascript:... 為例,我們可以這樣寫: 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 // 注銷代碼 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 [需要驗證的字符串] 27 * @return {[Boolean]} [false -- 驗證不通過,true -- 驗證通過] 28 */ 29 function blackListMatch(blackList, value) { 30 var length = blackList.length, 31 i = 0; 32 33 for (; i < length; i ) { 34 // 建立黑名單正則 XSS 跨站腳本的精髓不在于“跨站”,在于“腳本”。 通常而言,攻擊者或者運營商會向頁面中注入一個