前幾天看到一篇寫js文件反劫持的文章,想起15年主導做百度搜索結果頁面反劫持項目做得一些研究,整理成文章,跟大家分享。
常見劫持手段
按照劫持的方法不同,我將劫持分為下面兩類:
- 跳轉型劫持:用戶輸入地址A,但是跳轉到地址B
- 注入型劫持:有別於跳轉型型劫持,指通過在正常的網頁中注入廣告代碼(js、iframe等),實現頁面彈窗提醒或者底部廣告等,又分為下面三個小類:
- 注入js類劫持:在正常頁面注入劫持的js代碼實現的劫持
- iframe類劫持:將正常頁面嵌入iframe或者頁面增加iframe頁面
- 篡改頁面類劫持:正常頁面出現多余的劫持網頁標簽,導致頁面整體大小發生變化
跳轉型劫持
為了獲取流量,一些電商或者類似百度這樣需要流量合作的網站都會有自己的聯盟系統,通過給予一些獎勵來獲取導流,比如:百度或者電商會有渠道分成。
為了區分哪些是第三方給予導流過來的,通常會在url地址增加類似source、from之類的參數,或者進入頁面之前通過「中間頁」種cookie。
這樣,當用戶輸入一個正常網址的時候,劫持方會在網絡層讓其跳轉到帶分成或者渠道號的「中間頁」或者帶渠道號的頁面。這樣用戶進行下單或者搜索等行為,劫持方會得到「佣金」。
上面說的這類case還算友好,至少用戶一般體驗不到頁面變化,還有類似跳轉到釣魚網站的case,也有不正當競爭的case:用戶輸入baidu.com跳轉到so.com或者sm.cn,而對方網站有故意做成和百度搜索差不多的樣子,那時候也幫助法務做了很多案例收集。
題外話:前些年,用戶使用百度搜索某些醫療類query,立即用戶就會收到電話推廣醫院,很多用戶投訴,不明真相的群眾也指責百度,實際這類是運營商把url的關鍵詞賣給了醫療機構,百度只不過是躺槍。。。那時候還做了個項目是加密query。。。
注入型劫持
頁面在傳輸的過程中,被網絡層進行內容「再加工」,常見有:注入js、iframe、篡改頁面。
注入js
注入js的方式可以通過document.write
或者直接改html代碼片段等方式,給頁面增加外鏈js,為了做到更難檢測,有些運營商會捏造一個不存在的url地址,從而不被過濾或者檢測。
案例1:運營商會用自己識別的ip或者域名做js網址,wap.zjtoolbar.10086.cn這類只有在浙江移動網絡下才會被解析出來,同理ip也是
案例2:運營商很聰明,知道頁面可以檢測所有外鏈js的域名,比如:m.baidu.com我只允許m.baidu.com/static的外鏈js,其他js都會被記錄反饋;為了不被檢測出來,我遇見個case電信會訪問一個不存在的地址,比如:m.baidu.com/static/abc.js,這個地址在運營商直接返回劫持的js代碼,請求不會發到百度的服務器。
被放入iframe或者iframe其他頁面
這類case比較少見,但是一些擦邊球的網站或者沒有內容的垃圾站會用這種方式,他們一般是通過熱門關鍵詞之類做SEO,打開網站實際去了廣告之類沒有任何實際內容,而頁面卻是內嵌了一個其他網站,我們要是識別出來不被內嵌就需要檢測。
篡改頁面內容
這類case很少見,一般是在頁面底部增加js之外的div,然后展現一些非網站內容。
劫持檢測方法
講了常見的劫持手段有哪些,我們再來看看怎么識別上面提到的這些劫持。

上圖是15年8月11日這天百度某頁面的劫持情況,那天數據還算不錯,之前浙江移動網絡劫持率高達40%+,多數劫持來自zjtoolbar.10086.cn
這個域名,就是移動的流量提示(還專門啟用個域名zjtoolbar,浙江toolbar)。。。
跳轉型劫持
跳轉型劫持如果用單純靠Web頁面進行檢測比較困難,當時我們做檢測是在手機百度(手百)內做檢測,所以比較簡單,用戶輸入搜索詞(query),打開百度的頁面URL,然后當頁面加載結束,APP對比訪問的URL是否是之前要訪問的URL,如果URL不一致,則記錄上報。
注入js類頁面
- 改寫
document.write
方法 - 遍歷頁面
script
標簽,給外鏈js增加白名單,不在白名單內js外鏈都上報
檢測是否被iframe嵌套
這個通過比較parent
對象,如果頁面被嵌套,則parent!==window
,要獲取我們頁面的URL地址,可以使用下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
|
function getParentUrl() {
var url;
if (parent !== window) {
try {
url = parent.location.href;
} catch (e) {
url = document.referrer;
}
}
return url;
}
|
特殊方法
前面提到類似電信捏造在白名單內的js URL和篡改頁面內容的,我們用上面提到的方法檢測不到這些信息,如果是在APP內,可以做的事情就比較多了,除了上面之外,還可以比較頁面的content-length
。當時手百的做法是:
在用戶開始輸入query的時候,APP訪問一個空白頁面,頁面內只有html、title、head、body、script,而script標簽內主要代碼就是嗅探是否被劫持。
因為一般劫持不會針對某個頁面,而是針對整個網站域名,所以我們的空白頁面也會被劫持。
一旦被劫持,那么這么簡單的頁面結構就很容易做頁面劫持分析,分析出來劫持手段就上報case
script內核心代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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);
}
|
這樣做除了可以檢測到多余的js外鏈,還可以檢測出來篡改頁面內容等case。除了檢測域名劫持之外,在用戶輸入query的時刻訪問空白的頁面也可以提前完成DNS解析,另外還可以做劫持防御,所謂「一石三鳥」!
劫持防御
最簡單粗暴的就是直接上HTTPS
,一勞永逸。再就是取證,去打官司或者警告渠道作弊者。除此之外,我們還可以繼續利用空白頁面做劫持檢測。
手百在沒有全量https時期(畢竟全站https牽扯的工作量不小),利用空白頁面嗅探出當前網絡環境存在劫持風險的時候,那么就通過調用客戶端的接口,告訴客戶端本次啟動期間使用https
,這樣既可以降低劫持風險,又可以通過這個頁面小流量測試https數據,將來https全量后,還可以通過空白頁面將老版本的APP全量打開https。