1、對於DIV注入的,可以初始化時檢查全部html代碼。
檢測是否被劫持比較簡單,但對抗就略麻煩,這個在說完第2點之后再解釋。
2、對於js注入,可以在window監聽DOMNodeInserted事件。
事件有srcElement,可以獲取到剛插入的dom節點。
這里開始簡單粗暴的做正則匹配,匹配所有url。
再逐個比較是否白名單域名,如果不是,則判定為劫持。可以上報,同時可以移除dom.parentNode.removeChild(dom);
但這樣容易造成誤傷,因為正常頁面中可能有外部鏈接,或者一些純文本url。
function checkDivHijack(e) {
var html = e ? (e.srcElement.outerHTML || e.srcElement.wholeText) : $('html').html();
var reg = /http:\/\/([\w.:]+\/)[^'"\s]+/g;
var urlList = html.match(reg);
if (!urlList || urlList.length == 0) {
return;
}
reg = /^http:\/\/(.*\.qq\.com|.*\.gtimg\.cn|.*\.qlogo\.cn|.*\.qpic\.cn|.*\.wanggou\.com|.*\.jd\.com)\/$/;
var hijack = false;
for (var i = 0; i < urlList.length; i++) {
if (!reg.test(urlList[i])) {
hijack = true;
break;
}
}
}
后來改為
function checkDivHijack(e) {
var dom = e ? e.srcElement : document.documentElement;
if (!dom.outerHTML) {
return; //e不是一個dom,只是插入一段文本
}
var imgList = (dom.nodeName.toUpperCase() == 'IMG') ? [dom] : dom.getElementsByTagName('img');
if (!imgList || imgList.length == 0) {
return;
}
var httpReg = /^http:\/\/(.*\.qq\.com|.*\.gtimg\.cn|.*\.qlogo\.cn|.*\.qpic\.cn)\//;
var base64Reg = /^data:image/;
var src;
var hijack = false;
for (var i = 0; i < imgList.length; i++) {
src = imgList[i].src;
if (!httpReg.test(src) && !base64Reg.test(src)) {
hijack = true;
break;
}
}
}
但這樣也有漏洞,如果運營商通過div+style設置背景的方式顯示廣告圖,上述代碼就無法檢查出來。
那么,就還需要檢查style的情況,但style情況就更復雜了。可能是<style>,也可能是inline樣式,最終還是要回到url識別上。
那么做個折衷,我們繼續用最初的純文本正則匹配url的方式,但跳過純文本的情況(例如修改div的內容,替換為一段文本),只檢查插入dom的情況。
具體方法是
if (!dom.outerHTML) {
return; //e不是一個dom,只是插入一段文本
}
回到剛才第一點的問題,監測第一點的情況,可以用一樣的做法。但是,對抗就麻煩很多,因為廣告dom節點可以插在body第一層,也可以插在某個內容div中。如果簡單粗暴的把廣告dom節點到body的全部div都移除,可能會造成大面積的誤傷。
所以,針對這個情況,我們還在做進一步的監測統計。
3、對於iframe的情況,要檢測非常簡單,只需要比較self和top是否相同。
不過,要完整解決這個嵌套劫持,就要知道運營商的小把戲。
試想一下,iframe前,請求http://www.host.com/xxx.html ,就被劫持,302重定向到一個iframe的頁面,這個頁面使用iframe重新加載我們原來要請求的html。
那么,此時在iframe中的html為什么能夠順利加載回來呢?而不是又被劫持?
我們猜想,運營商應該在url中加了一個參數,標記是否已經劫持過。
而實際監測發現,我們的猜想也是正確的。
呃,我們仔細看,還可以發現運營商做這個劫持也非常粗暴,如果頁面依賴hash,就會引起錯誤了。
見招拆招,這個比較好辦,我們只需要把top的地址修改為self地址即可。一來沖掉iframe,二來繞過劫持。
function checkIframeHijack() {
var flag = 'iframe_hijack_redirected';
if (getURLParam(flag)) {
sendHijackReport('jiankang.hijack.iframe_ad', 'iframe hijack: ' + location.href);
} else {
if (self != top) {
var url = location.href;
var parts = url.split('#');
if (location.search) {
parts[0] += '&' + flag + '=1';
} else {
parts[0] += '?' + flag + '=1';
}
try {
top.location = parts.join('#');
} catch (e) {
}
}
}
}
為了安全起見,防止運營商有新招數,所以這里只嘗試一次,用iframe_hijack_redirected參數標記,已經嘗試過。