Web前端頁(yè)面劫持和反劫持
常見劫持手段
按照劫持的方法不同,我將劫持分為下面兩類:
- 跳轉(zhuǎn)型劫持:用戶輸入地址A,但是跳轉(zhuǎn)到地址B
- 注入型劫持:有別于跳轉(zhuǎn)型型劫持,指通過在正常的網(wǎng)頁(yè)中注入廣告代碼(js、iframe等),實(shí)現(xiàn)頁(yè)面彈窗提醒或者底部廣告等,又分為下面三個(gè)小類:
- 注入js類劫持:在正常頁(yè)面注入劫持的js代碼實(shí)現(xiàn)的劫持
- iframe類劫持:將正常頁(yè)面嵌入iframe或者頁(yè)面增加iframe頁(yè)面
- 篡改頁(yè)面類劫持:正常頁(yè)面出現(xiàn)多余的劫持網(wǎng)頁(yè)標(biāo)簽,導(dǎo)致頁(yè)面整體大小發(fā)生變化
跳轉(zhuǎn)型劫持
為了獲取流量,一些電商或者類似百度這樣需要流量合作的網(wǎng)站都會(huì)有自己的聯(lián)盟系統(tǒng),通過給予一些獎(jiǎng)勵(lì)來獲取導(dǎo)流,比如:百度或者電商會(huì)有渠道分成。
為了區(qū)分哪些是第三方給予導(dǎo)流過來的,通常會(huì)在url地址增加類似source、from之類的參數(shù),或者進(jìn)入頁(yè)面之前通過「中間頁(yè)」種cookie。
這樣,當(dāng)用戶輸入一個(gè)正常網(wǎng)址的時(shí)候,劫持方會(huì)在網(wǎng)絡(luò)層讓其跳轉(zhuǎn)到帶分成或者渠道號(hào)的「中間頁(yè)」或者帶渠道號(hào)的頁(yè)面。這樣用戶進(jìn)行下單或者搜索等行為,劫持方會(huì)得到「?jìng)蚪稹埂?/p>
上面說的這類case還算友好,至少用戶一般體驗(yàn)不到頁(yè)面變化,還有類似跳轉(zhuǎn)到釣魚網(wǎng)站的case,也有不正當(dāng)競(jìng)爭(zhēng)的case:用戶輸入baidu.com跳轉(zhuǎn)到so.com或者sm.cn,而對(duì)方網(wǎng)站有故意做成和百度搜索差不多的樣子,那時(shí)候也幫助法務(wù)做了很多案例收集。
題外話:前些年,用戶使用百度搜索某些醫(yī)療類query,立即用戶就會(huì)收到電話推廣醫(yī)院,很多用戶投訴,不明真相的群眾也指責(zé)百度,實(shí)際這類是運(yùn)營(yíng)商把url的關(guān)鍵詞賣給了醫(yī)療機(jī)構(gòu),百度只不過是躺槍。。。那時(shí)候還做了個(gè)項(xiàng)目是加密query。。。
注入型劫持
頁(yè)面在傳輸?shù)倪^程中,被網(wǎng)絡(luò)層進(jìn)行內(nèi)容「再加工」,常見有:注入js、iframe、篡改頁(yè)面。
注入js
注入js的方式可以通過 document.write或者直接改html代碼片段等方式,給頁(yè)面增加外鏈js,為了做到更難檢測(cè),有些運(yùn)營(yíng)商會(huì)捏造一個(gè)不存在的url地址,從而不被過濾或者檢測(cè)。
案例1:運(yùn)營(yíng)商會(huì)用自己識(shí)別的ip或者域名做js網(wǎng)址,wap.zjtoolbar.10086.cn這類只有在浙江移動(dòng)網(wǎng)絡(luò)下才會(huì)被解析出來,同理ip也是
案例2:運(yùn)營(yíng)商很聰明,知道頁(yè)面可以檢測(cè)所有外鏈js的域名,比如:m.baidu.com我只允許m.baidu.com/static的外鏈js,其他js都會(huì)被記錄反饋;為了不被檢測(cè)出來,我遇見個(gè)case電信會(huì)訪問一個(gè)不存在的地址,比如:m.baidu.com/static/abc.js,這個(gè)地址在運(yùn)營(yíng)商直接返回劫持的js代碼,請(qǐng)求不會(huì)發(fā)到百度的服務(wù)器。
被放入iframe或者iframe其他頁(yè)面
這類case比較少見,但是一些擦邊球的網(wǎng)站或者沒有內(nèi)容的垃圾站會(huì)用這種方式,他們一般是通過熱門關(guān)鍵詞之類做SEO,打開網(wǎng)站實(shí)際去了廣告之類沒有任何實(shí)際內(nèi)容,而頁(yè)面卻是內(nèi)嵌了一個(gè)其他網(wǎng)站,我們要是識(shí)別出來不被內(nèi)嵌就需要檢測(cè)。
篡改頁(yè)面內(nèi)容
這類case很少見,一般是在頁(yè)面底部增加js之外的div,然后展現(xiàn)一些非網(wǎng)站內(nèi)容。
劫持檢測(cè)方法
講了常見的劫持手段有哪些,我們?cè)賮砜纯丛趺醋R(shí)別上面提到的這些劫持。
上圖是15年8月11日這天百度某頁(yè)面的劫持情況,那天數(shù)據(jù)還算不錯(cuò),之前浙江移動(dòng)網(wǎng)絡(luò)劫持率高達(dá)40%+,多數(shù)劫持來自 zjtoolbar.10086.cn這個(gè)域名,就是移動(dòng)的流量提示(還專門啟用個(gè)域名zjtoolbar,浙江toolbar)。。。
跳轉(zhuǎn)型劫持
跳轉(zhuǎn)型劫持如果用單純靠Web頁(yè)面進(jìn)行檢測(cè)比較困難,當(dāng)時(shí)我們做檢測(cè)是在手機(jī)百度(手百)內(nèi)做檢測(cè),所以比較簡(jiǎn)單,用戶輸入搜索詞(query),打開百度的頁(yè)面URL,然后當(dāng)頁(yè)面加載結(jié)束,APP對(duì)比訪問的URL是否是之前要訪問的URL,如果URL不一致,則記錄上報(bào)。
注入js類頁(yè)面
- 改寫 document.write方法
- 遍歷頁(yè)面 script標(biāo)簽,給外鏈js增加白名單,不在白名單內(nèi)js外鏈都上報(bào)
檢測(cè)是否被iframe嵌套
這個(gè)通過比較 parent對(duì)象,如果頁(yè)面被嵌套,則 parent!==window,要獲取我們頁(yè)面的URL地址,可以使用下面的代碼:
- function getParentUrl() {
- var url;
- if (parent !== window) {
- try {
- url = parent.location.href;
- } catch (e) {
- url = document.referrer;
- }
- }
- return url;
- }
特殊方法
前面提到類似電信捏造在白名單內(nèi)的js URL和篡改頁(yè)面內(nèi)容的,我們用上面提到的方法檢測(cè)不到這些信息,如果是在APP內(nèi),可以做的事情就比較多了,除了上面之外,還可以比較頁(yè)面的 content-length。當(dāng)時(shí)手百的做法是:
在用戶開始輸入query的時(shí)候,APP訪問一個(gè)空白頁(yè)面,頁(yè)面內(nèi)只有html、title、head、body、script,而script標(biāo)簽內(nèi)主要代碼就是嗅探是否被劫持。
因?yàn)橐话憬俪植粫?huì)針對(duì)某個(gè)頁(yè)面,而是針對(duì)整個(gè)網(wǎng)站域名,所以我們的空白頁(yè)面也會(huì)被劫持。
一旦被劫持,那么這么簡(jiǎn)單的頁(yè)面結(jié)構(gòu)就很容易做頁(yè)面劫持分析,分析出來劫持手段就上報(bào)case
script內(nèi)核心代碼如下:
- function hiJackSniffer() {
- var files = $.toArray(D.querySelectorAll('script[src]'));
- var arr = [];
- for (var i = 0, len = files.length; i < len; i++) {
- files[i].src && arr.push(files[i].src);
- }
- if (arr.length) {
- return sendImg(arr, 1);
- }
- arr = getParentUrl();
- if (arr && arr.length) {
- //被嵌入iframe
- return sendImg([arr], 2);
- }
- if (D.documentElement.outerHTML.length > 4e3) {
- var tmp = {};
- var headjs = $.toArray(D.head.querySelectorAll('script'));
- var unknownCode = [];
- if (headjs.length) {
- unknownCode = unknownCode.concat(headjs.map(function(v) {
- return v.innerHTML;
- }).filter(function(v) {
- return !!v;
- }));
- }
- var body = $.toArray(D.body.querySelectorAll('*'));
- if (body.length > 1) {
- unknownCode = unknownCode.concat(body.map(function(v) {
- return v.outerHTML.split('\n').join('');
- }).filter(function(str) {
- if (/^<script id="b">/.test(str)) {
- return false;
- }
- return true;
- }));
- }
- return sendImg(unknownCode, 3);
- }
- sendImg([], 0);
- }
這樣做除了可以檢測(cè)到多余的js外鏈,還可以檢測(cè)出來篡改頁(yè)面內(nèi)容等case。除了檢測(cè)域名劫持之外,在用戶輸入query的時(shí)刻訪問空白的頁(yè)面也可以提前完成DNS解析,另外還可以做劫持防御,所謂「一石三鳥」!
劫持防御
最簡(jiǎn)單粗暴的就是直接上 HTTPS,一勞永逸。再就是取證,去打官司或者警告渠道作弊者。除此之外,我們還可以繼續(xù)利用空白頁(yè)面做劫持檢測(cè)。
手百在沒有全量https時(shí)期(畢竟全站https牽扯的工作量不小),利用空白頁(yè)面嗅探出當(dāng)前網(wǎng)絡(luò)環(huán)境存在劫持風(fēng)險(xiǎn)的時(shí)候,那么就通過調(diào)用客戶端的接口,告訴客戶端本次啟動(dòng)期間使用 https,這樣既可以降低劫持風(fēng)險(xiǎn),又可以通過這個(gè)頁(yè)面小流量測(cè)試https數(shù)據(jù),將來https全量后,還可以通過空白頁(yè)面將老版本的APP全量打開https。
【本文為51CTO專欄作者“三水清”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】