淺談動態爬蟲與去重(續)

作者:Fr1day@0keeTeam

0x00 前言

在 淺談動態爬蟲與去重 中,分享了動態爬蟲中觸發事件、監控節點變動、URL去重等的實現方法。在接近一年的線上運行與迭代更新過程中,處理了很多bug,也遇到一些有趣的漏抓案例。

本文將詳細分析幾個有代表性的案例,希望能對各位的coding大業有所幫助。

0x01 一個都不能少

上圖為被抓取頁面的源碼。兩個 <a> 標籤點擊後會分別跳轉到 /test4.php/test5.php(抓取時頁面已鎖定,實際不會跳轉,但可以監控到跳轉的目標URL)。

但爬蟲並未抓取到 http://localhost/test4.php。檢查下phantomjs模塊的具體調用日誌:

可以看到 id=test4 節點對應的事件確實被觸發了,而且還把錨點的變化都記錄下來了。但就是沒抓到 /test4.php 跳轉的請求。

原本以為是因為錨點的問題,錨點阻止了後續的事件執行(一個不負責任的腦洞)。但經過一番調試,發現是觸發事件的時候出問題了。

window.location.href 重複執行的時候,瀏覽器只會執行後面的一個,比如這段代碼,可以粘貼到 Console 執行下,頁面會跳轉到 /456。

解決方案如下圖,由於 Javascript 的非同步非阻塞的特性,還加了個閉包來實現 sleep。

如果不想這麼做的話,也可以通過 Hook location對象來解決。

一句話總結:觸發事件一定要有時間間隔!

0x02 猝不及防的關閉

<script type="text/javascript" language="javascript"> function BtnPsw_onclick() {

window.open("../Login/UpdatePass")

}

</script>
<form name="form1" method="post" action="" id="form1">

用戶名:<input
name="UserName" type="text" id="UserName" class="logintext"><br/>

密碼: <input
name="Password" type="password" id="Password" class="logintext"><br/> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="1222"> <input name="LoginButton" type="button" id="LoginButton" value="登 錄" class="lbotton"> <input name="login" type="button" value="退 出" class="lbotton" onclick="window.close()"> <input name="UpdatePwd" type="button" value="修改密碼" class="lbotton" onclick="BtnPsw_onclick()"> </form>

phantomjs解析的時候,超時嚴重導致漏抓。通過偉大的注釋調試法,可以發現問題在這行代碼里:n

<input name="login" type="button" value="退 出" class="lbotton" onclick="window.close()">

動態分析時會主動去執行行內綁定的代碼,即:window.close()。關閉了頁面之後,PhantomJS後續綁定的事件都會失效,比如page.evaluate、page.onCallback、phantom.exit。沒有執行exit函數,一直阻塞導致觸發python的超時——狗帶。n

修復方案,在執行關閉頁面的時候,PhantomJS的onClosing事件可以收到通知,示例代碼如下:

還可以通過Hook來解決這個問題:

0x03 永遠觸發不完的事件

案例URL: lvyou.baidu.com/main/ev

動態分析超時導致沒有結果返回。動態爬蟲里觸發行內綁定事件的代碼如下:

邏輯是遍歷所有的節點的所有屬性,執行以on開頭的屬性值,即 onclick=alert(1) 這種。

但是抓取上面案例的時候,發現一直沒有返回結果,使用偉大的print調試法列印了觸發的具體內容後,發現頁面一直在不停的觸發同一個事件。

仔細看下頁面源碼:

登錄應該是用JSONP來實現的,每次點擊登錄都會生成一個script標籤,而且這個標籤恰好還插入在了登錄標籤前面。

遍曆數組的過程中,也在不斷擴展這個數組。這就是問題的關鍵。

那應該怎麼解決呢?用了個不太優雅的方法來實現JS深拷貝:

0x04 Hook是個哲學問題

<a onclick=show()>test</a> <script type="text/javascript"> function show(){

window.showModalDialog("another_page.html");

}
</script>

如上為漏抓頁面的部分代碼。爬蟲進程超時,沒有返回任何數據。n

window.showModalDialog 是早期瀏覽器使用比較頻繁的函數,用來彈出一個新頁面,並且是阻塞執行的(所以造成爬蟲超時被強行殺進程)。後來被 window.open 函數替代。替換的原因有:

1. showModalDialog 沒有導航欄,無法進行後退、前進、收藏等操作

2. showModalDialog debug非常複雜(只能用alert調試法 2333)

3. 名字又長又難記(迷之猜測)

下圖為正常打開的頁面與 showModalDialog 打開的頁面比較:

目前Chrome最新版已經不支持這個函數了,但Firefox、Safari、IE仍然支持。毫無意外的 PhantomJS 也支持。解決方案很簡單,直接 Hook 函數就可以了:

這樣的話,加上最開始就被 Hook 的 alert/prompt/confirm,現在已經 Hook 了四個可能會引起阻塞的函數了,是不是還有其他隱藏的存在呢?

寫個腳本來檢查下:

var page = require(webpage).create();

page.onConsoleMessage = function(msg) {

console.log(> + msg );

return
true;

};

page.open("http://127.0.0.1:8082", "GET", "", function (status) {

console.log(status);

page.evaluateAsync(function(){

for(var i in
window){

try {

if (typeof
eval("window." + i) != "function") {

continue

}

}catch (e){

}

// if(i in {"showModalDialog": "1"}){
// continue // } try{

console.log(i)

eval("(function(){" + i + "();})()");

}

catch (e){

// console.log(e)

}

}

}, 10)

});

用 PhantomJS 載入任意頁面,然後遍歷 window 對象。首先出現阻塞卡頓的函數是 showModalDialog,再次運行腳本,跳過 showModalDialog 函數,然後….n

順暢的運行完成,說好的 alert/prompt/confirm 函數導致的阻塞呢?

複製腳本到瀏覽器中運行,倒是成功復現了 alert/prompt/confirm/print 導致的阻塞:

分析原因,應該是PhantomJS在封裝onAlert、onPrompt、onConfirm介面的時候就對這幾個可能產生阻塞的函數做了處理。

同樣的原理,可以套用在其他的動態解析器上。舉個栗子,在Chrome Headless里需要 Hook 哪些介面,你現在知道了嗎?

0x05 總結

雖然在 Chrome Headless 出來之後,PhantomJS 變得索然無味。但是同樣都基於Webkit內核,所遇到的問題和解決方案也大多相通,不必拘泥於形式。

如果有動態分析和爬蟲方面的問題/想法,歡迎微博私信我 @吃瓜群眾-Fr1day

註:水印圖片來自於「安全小黃鴨」,是我個人公眾號,不涉及版權問題。

本文由安全客原創發布

登錄安全客 - 有思想的安全新媒體www.anquanke.com/,或下載安全客APP來獲取更多最新資訊吧~


推薦閱讀:

Atom也爆遠程代碼執行漏洞?就問你怕不怕!
聖誕節專題 | 躲得過初一、躲得過十五,卻躲不過Cyber Monday!

TAG:爬虫计算机网络 | 互联网 | 信息安全 |