淺談動態爬蟲與去重(續)
作者: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: https://lvyou.baidu.com/main/event/rank
動態分析超時導致沒有結果返回。動態爬蟲里觸發行內綁定事件的代碼如下:
邏輯是遍歷所有的節點的所有屬性,執行以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 ); returntrue;};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!