如何突破網(wǎng)站對selenium的屏蔽mitmproxy

使用selenium模擬瀏覽器進(jìn)行數(shù)據(jù)抓取無疑是當(dāng)下最通用的數(shù)據(jù)采集方案,它通吃各種數(shù)據(jù)加載方式,能夠繞過客戶JS加密,繞過爬蟲檢測,繞過簽名機制。它的應(yīng)用,使得許多網(wǎng)站的反采集策略形同虛設(shè)。由于selenium不會在HTTP請求數(shù)據(jù)中留下指紋,因此無法被網(wǎng)站直接識別和攔截。

這是不是就意味著selenium真的就無法被網(wǎng)站屏蔽了呢?非也。selenium在運行的時候會暴露出一些預(yù)定義的Javascript變量(特征字符串),例如"window.navigator.webdriver",在非selenium環(huán)境下其值為undefined,而在selenium環(huán)境下,其值為true(如下圖所示為selenium驅(qū)動下Chrome控制臺打印出的值)。

image.png

除此之外,還有一些其它的標(biāo)志性字符串(不同的瀏覽器可能會有所不同),常見的特征串如下所示:

webdriver  
__driver_evaluate  
__webdriver_evaluate  
__selenium_evaluate  
__fxdriver_evaluate  
__driver_unwrapped  
__webdriver_unwrapped  
__selenium_unwrapped  
__fxdriver_unwrapped  
_Selenium_IDE_Recorder  
_selenium  
calledSelenium  
_WEBDRIVER_ELEM_CACHE  
ChromeDriverw  
driver-evaluate  
webdriver-evaluate  
selenium-evaluate  
webdriverCommand  
webdriver-evaluate-response  
__webdriverFunc  
__webdriver_script_fn  
__$webdriverAsyncExecutor  
__lastWatirAlert  
__lastWatirConfirm  
__lastWatirPrompt  
$chrome_asyncScriptInfo  
$cdc_asdjflasutopfhvcZLmcfl_  

了解了這個特點之后,就可以在瀏覽器客戶端JS中通過檢測這些特征串來判斷當(dāng)前是否使用了selenium,并將檢測結(jié)果附加到后續(xù)請求之中,這樣服務(wù)端就能識別并攔截后續(xù)的請求。

下面講一個具體的例子。

鯤之鵬的技術(shù)人員近期就發(fā)現(xiàn)了一個能夠有效檢測并屏蔽selenium的網(wǎng)站應(yīng)用:驗證碼表單頁,如果是正常的瀏覽器操作,能夠有效的通過驗證,但如果是使用selenium就會被識別,即便驗證碼輸入正確,也會被提示“請求異常,拒絕操作”,無法通過驗證(如下圖所示)。
image.png

可以看到它檢測了"webdriver", "__driver_evaluate", "__webdriver_evaluate"等等這些selenium的特征串。提交驗證碼的時候抓包可以看到一個_token參數(shù)(很長),selenium檢測結(jié)果應(yīng)該就包含在該參數(shù)里,服務(wù)端借以判斷“請求異常,拒絕操作”。

現(xiàn)在才進(jìn)入正題,如何突破網(wǎng)站的這種屏蔽呢?

我們已經(jīng)知道了屏蔽的原理,只要我們能夠隱藏這些特征串就可以了。但是還不能直接刪除這些屬性,因為這樣可能會導(dǎo)致selenium不能正常工作了。我們采用曲線救國的方法,使用中間人代理,比如fidder, proxy2.py或者mitmproxy,將JS文件(本例是yoda.*.js這個文件)中的特征字符串給過濾掉(或者替換掉,比如替換成根本不存在的特征串),讓它無法正常工作,從而達(dá)到讓客戶端腳本檢測不到selenium的效果。

下面我們驗證下這個思路。這里我們使用mitmproxy實現(xiàn)中間人代理),對JS文件(本例是yoda.*.js這個文件)內(nèi)容進(jìn)行過濾。啟動mitmproxy代理并加載response處理腳本:

mitmdump.exe -S modify_response.py

其中modify_response.py腳本如下所示:

# coding: utf-8  
# modify_response.py  
  
import re  
from mitmproxy import ctx  
    
def response(flow):  
  """修改應(yīng)答數(shù)據(jù) 
  """  
  if '/js/yoda.' in flow.request.url:  
      # 屏蔽selenium檢測 
      for webdriver_key in ['webdriver', '__driver_evaluate', '__webdriver_evaluate', '__selenium_evaluate', '__fxdriver_evaluate', '__driver_unwrapped', '__webdriver_unwrapped', '__selenium_unwrapped', '__fxdriver_unwrapped', '_Selenium_IDE_Recorder', '_selenium', 'calledSelenium', '_WEBDRIVER_ELEM_CACHE', 'ChromeDriverw', 'driver-evaluate', 'webdriver-evaluate', 'selenium-evaluate', 'webdriverCommand', 'webdriver-evaluate-response', '__webdriverFunc', '__webdriver_script_fn', '__$webdriverAsyncExecutor', '__lastWatirAlert', '__lastWatirConfirm', '__lastWatirPrompt', '$chrome_asyncScriptInfo', '$cdc_asdjflasutopfhvcZLmcfl_']:  
          ctx.log.info('Remove "{}" from {}.'.format(webdriver_key, flow.request.url))  
          flow.response.text = flow.response.text.replace('"{}"'.format(webdriver_key), '"NO-SUCH-ATTR"')  
      flow.response.text = flow.response.text.replace('t.webdriver', 'false')  
      flow.response.text = flow.response.text.replace('ChromeDriver', '') 

在selnium中使用該代理(mitmproxy默認(rèn)監(jiān)聽127.0.0.1:8080)訪問目標(biāo)網(wǎng)站,mitmproxy將過濾JS中的特征符串,如下圖所示:


image.png

經(jīng)多次測試,該方法可以有效的繞過selenium檢測,成功提交驗證碼表單。

抄自:http://www.site-digger.com/html/articles/20180821/653.html
成功


附上mitmproxy簡單介紹(格式渣,可以直接訪問原文)
抄自 https://blog.wolfogre.com/posts/usage-of-mitmproxy/


本文是一個較為完整的 mitmproxy 教程,側(cè)重于介紹如何開發(fā)攔截腳本,幫助讀者能夠快速得到一個自定義的代理工具。

本文假設(shè)讀者有基本的 python 知識,且已經(jīng)安裝好了一個 python 3 開發(fā)環(huán)境。如果你對 nodejs 的熟悉程度大于對 python,可移步到 anyproxy,anyproxy 的功能與 mitmproxy 基本一致,但使用 js 編寫定制腳本。除此之外我就不知道有什么其他類似的工具了,如果你知道,歡迎評論告訴我。

本文基于 mitmproxy v4,當(dāng)前版本號為 [v4.0.1]
(https://blog.wolfogre.com/redirect/v3/Ax7R98RpJpVlv3sgL6mHzyQSAwM8Cv46xcU7LxImWv3FLS8tPHP6U8UtLy08c_pTxSFXLjbFyDvFbf40bv4wbv4xMRIDAzwK_jrFxVoWBjtuQQYW3Dsh_cU8Bk0KxaQE-cwFzC0vLTxz-lPF)。

顧名思義,mitmproxy 就是用于 MITM 的 proxy,MITM 即中間人攻擊(Man-in-the-middle attack)。用于中間人攻擊的代理首先會向正常的代理一樣轉(zhuǎn)發(fā)請求,保障服務(wù)端與客戶端的通信,其次,會適時的查、記錄其截獲的數(shù)據(jù),或篡改數(shù)據(jù),引發(fā)服務(wù)端或客戶端特定的行為。

不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不僅可以截獲請求幫助開發(fā)者查看、分析,更可以通過自定義腳本進(jìn)行二次開發(fā)。舉例來說,利用 fiddler 可以過濾出瀏覽器對某個特定 url 的請求,并查看、分析其數(shù)據(jù),但實現(xiàn)不了高度定制化的需求,類似于:“截獲對瀏覽器對該 url 的請求,將返回內(nèi)容置空,并將真實的返回內(nèi)容存到某個數(shù)據(jù)庫,出現(xiàn)異常時發(fā)出郵件通知”。而對于 mitmproxy,這樣的需求可以通過載入自定義 python 腳本輕松實現(xiàn)。

但 mitmproxy 并不會真的對無辜的人發(fā)起中間人攻擊,由于 mitmproxy 工作在 HTTP 層,而當(dāng)前 HTTPS 的普及讓客戶端擁有了檢測并規(guī)避中間人攻擊的能力,所以要讓 mitmproxy 能夠正常工作,必須要讓客戶端(APP 或瀏覽器)主動信任 mitmproxy 的 SSL 證書,或忽略證書異常,這也就意味著 APP 或瀏覽器是屬于開發(fā)者本人的——顯而易見,這不是在做黑產(chǎn),而是在做開發(fā)或測試。

那這樣的工具有什么實際意義呢?據(jù)我所知目前比較廣泛的應(yīng)用是做仿真爬蟲,即利用手機模擬器、無頭瀏覽器來爬取 APP 或網(wǎng)站的數(shù)據(jù),mitmproxy 作為代理可以攔截、存儲爬蟲獲取到的數(shù)據(jù),或修改數(shù)據(jù)調(diào)整爬蟲的行為。

事實上,以上說的僅是 mitmproxy 以正向代理模式工作的情況,通過調(diào)整配置,mitmproxy 還可以作為透明代理、反向代理、上游代理、SOCKS 代理等,但這些工作模式針對 mitmproxy 來說似乎不大常用,故本文僅討論正向代理模式。

安裝
“安裝 mitmproxy”這句話是有歧義的,既可以指“安裝 mitmproxy 工具”,也可以指“安裝 python 的 mitmproxy 包”,注意后者是包含前者的。
pip install mitmproxy (windows)
運行
要啟動 mitmproxy 用 mitmproxy、mitmdump、mitmweb 這三個命令中的任意一個即可,這三個命令功能一致,且都可以加載自定義腳本,唯一的區(qū)別是交互界面的不同。(我一般使用 mitmdump -s 腳本.py 命令運行)
腳本
腳本的編寫需要遵循 mitmproxy 規(guī)定的套路,這樣的套路有兩個,使用時選其中一個套路即可。
第一個套路是,編寫一個 py 文件供 mitmproxy 加載,文件中定義了若干函數(shù),這些函數(shù)實現(xiàn)了某些 mitmproxy 提供的事件,mitmproxy 會在某個事件發(fā)生時調(diào)用對應(yīng)的函數(shù),形如:

import mitmproxy.http
from mitmproxy import ctx

num = 0


def request(flow: mitmproxy.http.HTTPFlow):
    global num
    num = num + 1
    ctx.log.info("We've seen %d flows" % num)

第二個套路(我沒成功)是,編寫一個 py 文件供 mitmproxy 加載,文件定義了變量 addons,addons 是個數(shù)組,每個元素是一個類實例,這些類有若干方法,這些方法實現(xiàn)了某些 mitmproxy 提供的事件,mitmproxy 會在某個事件發(fā)生時調(diào)用對應(yīng)的方法。這些類,稱為一個個 addon,比如一個叫 Counter 的 addon:

import mitmproxy.http
from mitmproxy import ctx


class Counter:
    def __init__(self):
        self.num = 0

    def request(self, flow: mitmproxy.http.HTTPFlow):
        self.num = self.num + 1
        ctx.log.info("We've seen %d flows" % self.num)


addons = [
    Counter()
]

事件

上述的腳本估計不用我解釋相信大家也看明白了,就是當(dāng) request 發(fā)生時,計數(shù)器加一,并打印日志。這里對應(yīng)的是 request 事件,那攏共有哪些事件呢?不多,也不少,這里詳細(xì)介紹一下。

事件針對不同生命周期分為 5 類?!吧芷凇边@里指在哪一個層面看待事件,舉例來說,同樣是一次 web 請求,我可以理解為“HTTP 請求 -> HTTP 響應(yīng)”的過程,也可以理解為“TCP 連接 -> TCP 通信 -> TCP 斷開”的過程。那么,如果我想拒絕來個某個 IP 的客戶端請求,應(yīng)當(dāng)注冊函數(shù)到針對 TCP 生命周期 的 tcp_start 事件,又或者,我想阻斷對某個特定域名的請求時,則應(yīng)當(dāng)注冊函數(shù)到針對 HTTP 聲明周期的 http_connect 事件。其他情況同理。

下面一段估計會又臭又長,如果你沒有耐心看完,那至少看掉針對 HTTP 生命周期的事件,然后跳到示例。
1,針對 HTTP 生命周期
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 收到了來自客戶端的 HTTP CONNECT 請求。在 flow 上設(shè)置非 2xx 響應(yīng)將返回該響應(yīng)并斷開連接。CONNECT 不是常用的 HTTP 請求方法,目的是與服務(wù)器建立代理連接,僅是 client 與 proxy 的之間的交流,所以 CONNECT 請求不會觸發(fā) request、response 等其他常規(guī)的 HTTP 事件。

def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求的頭部被成功讀取。此時 flow 中的 request 的 body 是空的。

def request(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求被成功完整讀取。

def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務(wù)端的 HTTP 響應(yīng)的頭部被成功讀取。此時 flow 中的 response 的 body 是空的。

def response(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務(wù)端端的 HTTP 響應(yīng)被成功完整讀取。

def error(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 發(fā)生了一個 HTTP 錯誤。比如無效的服務(wù)端響應(yīng)、連接斷開等。注意與“有效的 HTTP 錯誤返回”不是一回事,后者是一個正確的服務(wù)端響應(yīng),只是 HTTP code 表示錯誤而已。

  1. 針對 TCP 生命周期
    (懶得抄)
  2. 針對 Websocket 生命周期
    (懶得抄)
  3. 針對網(wǎng)絡(luò)連接生命周期
    (懶得抄)
  4. 通用生命周期
    (懶得抄)
    事實上考慮到 mitmproxy 的實際使用場景,大多數(shù)情況下我們只會用到針對 HTTP 生命周期的幾個事件。再精簡一點,甚至只需要用到 http_connect、request、response 三個事件就能完成大多數(shù)需求了
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容