第32講:實時處理利器 mitmproxy 的使用

SpiderLiH發表於2020-10-03

在上一節課我們講解了 Charles 的使用,它可以幫助我們抓取 HTTP 和 HTTPS 的資料包,抓到請求之後,我們如果能夠分析出介面請求的一些規律,就能輕鬆通過 Python 指令碼來進行改寫。可是當請求裡面包含一些無規律的引數的時候,可能就束手無策了。本節課我們介紹一個叫作 mitmproxy 的工具,它可以對抓包的結果通過指令碼進行實時處理和儲存,接下來我們來一起了解下吧。

1.介紹

mitmproxy 是一個支援 HTTP 和 HTTPS 的抓包程式,有類似 Fiddler、Charles 的功能,只不過它是一個控制檯的形式操作。

mitmproxy 還有兩個關聯元件。一個是 mitmdump,它是 mitmproxy 的命令列介面,利用它我們可以對接 Python 指令碼,用 Python 實現實時監聽後的處理。另一個是 mitmweb,它是一個 Web 程式,通過它我們可以清楚觀察 mitmproxy 捕獲的請求。

下面我們來了解它們的用法。

2.準備工作

請確保已經正確安裝好了 mitmproxy,並且手機和 PC 處於同一個區域網下,同時配置好了 mitmproxy 的 CA 證照,具體的配置可以參考 https://cuiqingcai.com/5391.html

2.1mitmproxy 的功能

mitmproxy 有如下幾項功能。

  • 攔截 HTTP 和 HTTPS 請求和響應;
  • 儲存 HTTP 會話並進行分析;
  • 模擬客戶端發起請求,模擬服務端返回響應;
  • 利用反向代理將流量轉發給指定的伺服器;
  • 支援 Mac 和 Linux 上的透明代理;
  • 利用 Python 對 HTTP 請求和響應進行實時處理。

3.抓包原理

和 Charles 一樣,mitmproxy 執行於自己的 PC 上,mitmproxy 會在 PC 的 8080 埠執行,然後開啟一個代理服務,這個服務實際上是一個 HTTP/HTTPS 的代理。

手機和 PC 在同一個區域網內,設定代理為 mitmproxy 的代理地址,這樣手機在訪問網際網路的時候流量資料包就會流經 mitmproxy,mitmproxy 再去轉發這些資料包到真實的伺服器,伺服器返回資料包時再由 mitmproxy 轉發回手機,這樣 mitmproxy 就相當於起了中間人的作用,抓取到所有 Request 和 Response.

另外這個過程還可以對接 mitmdump,抓取到的 Request 和 Response 的具體內容都可以直接用 Python 來處理,比如得到 Response 之後我們可以直接進行解析,然後存入資料庫,這樣就完成了資料的解析和儲存過程。

4.設定代理

首先,我們需要執行 mitmproxy,mitmproxy 啟動命令如下所示:

mitmproxy

在這裡插入圖片描述
右下角會出現當前正在監聽的埠。

或者啟動 mitmdump,它也會監聽 8080 埠,命令如下所示:

mitmdump

執行結果如圖所示。
在這裡插入圖片描述
將手機和 PC 連線在同一區域網下,設定代理為當前代理。首先看看 PC 的當前區域網 IP。

Windows 上的命令如下所示:

ipconfig

Linux 和 Mac 上的命令如下所示:

ifconfig

輸出結果如圖所示。
在這裡插入圖片描述
一般類似 10…* 或 172.16… 或 192.168.1.* 這樣的 IP 就是當前 PC 的區域網 IP,例如此圖中 PC 的 IP 為 192.168.1.28,手機代理設定類似如圖所示。
在這裡插入圖片描述
這樣我們就配置好了 mitmproxy 的代理。

5.mitmproxy 的使用

確保 mitmproxy 正常執行,並且手機和 PC 處於同一個區域網內,設定了 mitmproxy 的代理。

執行 mitmproxy,命令如下所示:

mitmproxy

設定成功之後,我們只需要在手機瀏覽器上訪問任意的網頁或瀏覽任意的 App 即可。例如在手機上開啟百度,mitmproxy 頁面便會呈現出手機上的所有請求,如圖所示。
在這裡插入圖片描述
這就相當於之前我們在瀏覽器開發者工具監聽到的瀏覽器請求,在這裡我們藉助於 mitmproxy 完成。Charles 完全也可以做到。

這裡是剛才手機開啟百度頁面時的所有請求列表,左下角顯示的 2/38 代表一共發生了 38 個請求,當前箭頭所指的是第二個請求。

每個請求開頭都有一個 GET 或 POST,這是各個請求的請求方式。緊接的是請求的 URL。第二行開頭的數字就是請求對應的響應狀態碼,後面是響應內容的型別,如 text/html 代表網頁文件、image/gif 代表圖片。再往後是響應體的大小和響應的時間。

當前呈現了所有請求和響應的概覽,我們可以通過這個頁面觀察到所有的請求。

如果想檢視某個請求的詳情,我們可以敲擊回車,進入請求的詳情頁面,如圖所示。
在這裡插入圖片描述
可以看到 Headers 的詳細資訊,如 Host、Cookies、User-Agent 等。

最上方是一個 Request、Response、Detail 的列表,當前處在 Request 這個選項上。這時我們再點選 Tab 鍵,即可檢視這個請求對應的響應詳情,如圖所示。
在這裡插入圖片描述
最上面是響應頭的資訊,下拉之後我們可以看到響應體的資訊。針對當前請求,響應體就是網頁的原始碼。

這時再敲擊 Tab 鍵,切換到最後一個選項卡 Detail,即可看到當前請求的詳細資訊,如伺服器的 IP 和埠、HTTP 協議版本、客戶端的 IP 和埠等,如圖所示。
在這裡插入圖片描述
mitmproxy 還提供了命令列式的編輯功能,我們可以在此頁面中重新編輯請求。敲擊 e 鍵即可進入編輯功能,這時它會詢問你要編輯哪部分內容,如 Cookies、Query、URL 等,每個選項的第一個字母會高亮顯示。敲擊要編輯內容名稱的首字母即可進入該內容的編輯頁面,如敲擊 m 即可編輯請求的方式,敲擊 q 即可修改 GET 請求引數 Query。

這時我們敲擊 q,進入到編輯 Query 的頁面。由於沒有任何引數,我們可以敲擊 a 來增加一行,然後就可以輸入引數對應的 Key 和 Value,如圖所示。
在這裡插入圖片描述
這裡我們輸入 Key 為 wd,Value 為 NBA。

然後再敲擊 Esc 鍵和 q 鍵,返回之前的頁面,再敲擊 e 和 p 鍵修改 Path。和上面一樣,敲擊 a 增加 Path 的內容,這時我們將 Path 修改為 s,如圖所示。
在這裡插入圖片描述
再敲擊 esc 和 q 鍵返回,這時我們可以看到最上面的請求連結變成了 https://www.baidu.com/s?wd=NBA,訪問這個頁面,可以看到百度搜尋 NBA 關鍵詞的搜尋結果,如圖所示。
在這裡插入圖片描述
敲擊 a 儲存修改,敲擊 r 重新發起修改後的請求,即可看到上方請求方式前面多了一個迴旋箭頭,這說明重新執行了修改後的請求。這時我們再觀察響應體內容,即可看到搜尋 NBA 的頁面結果的原始碼,如圖所示。
在這裡插入圖片描述
以上內容便是 mitmproxy 的簡單用法。利用 mitmproxy,我們可以觀察到手機上的所有請求,還可以對請求進行修改並重新發起。

Fiddler、Charles 也有這個功能,而且它們的圖形介面操作更加方便。那麼 mitmproxy 的優勢何在?

mitmproxy 的強大之處體現在它的另一個工具 mitmdump,有了它我們可以直接對接 Python 對請求進行處理。下面我們來看看 mitmdump 的用法。

6.mitmdump 的使用

mitmdump 是 mitmproxy 的命令列介面,同時還可以對接 Python 對請求進行處理,這是相比 Fiddler、Charles 等工具更加方便的地方。有了它我們可以不用手動截獲和分析 HTTP 請求和響應,只需寫好請求和響應的處理邏輯即可。它還可以實現資料的解析、儲存等工作,這些過程都可以通過 Python 實現。

6.1例項引入

我們可以使用命令啟動 mitmproxy,並把截獲的資料儲存到檔案中,命令如下所示:

mitmdump -w outfile

其中 outfile 的名稱任意,截獲的資料都會被儲存到此檔案中。
還可以指定一個指令碼來處理截獲的資料,使用 - s 引數即可:

mitmdump -s script.py

這裡指定了當前處理指令碼為 script.py,它需要放置在當前命令執行的目錄下。
我們可以在指令碼里寫入如下的程式碼:

def request(flow):
   flow.request.headers['User-Agent'] = 'MitmProxy'
   print(flow.request.headers)

我們定義了一個 request 方法,引數為 flow,它其實是一個 HTTPFlow 物件,通過 request 屬性即可獲取到當前請求物件。然後列印輸出了請求的請求頭,將請求頭的 User-Agent 修改成了 MitmProxy。
執行之後我們在手機端訪問 http://httpbin.org/get,就可以看到有如下情況發生。

手機端的頁面顯示如圖所示。
在這裡插入圖片描述
PC 端控制檯輸出如圖所示。
在這裡插入圖片描述
手機端返回結果的 Headers 實際上就是請求的 Headers,User-Agent 被修改成了 mitmproxy。PC 端控制檯輸出了修改後的 Headers 內容,其 User-Agent 的內容正是 mitmproxy。

所以,通過這三行程式碼我們就可以完成對請求的改寫。print 方法輸出結果可以呈現在 PC 端控制檯上,可以方便地進行除錯。

6.2日誌輸出

mitmdump 提供了專門的日誌輸出功能,可以設定不同級別以不同顏色輸出結果。我們把指令碼修改成如下內容:

from mitmproxy import ctx
def request(flow):
   flow.request.headers['User-Agent'] = 'MitmProxy'
   ctx.log.info(str(flow.request.headers))
   ctx.log.warn(str(flow.request.headers))
   ctx.log.error(str(flow.request.headers))

這裡呼叫了 ctx 模組,它有一個 log 功能,呼叫不同的輸出方法就可以輸出不同顏色的結果,以方便我們做除錯。例如,info 方法輸出的內容是白色的,warn 方法輸出的內容是黃色的,error 方法輸出的內容是紅色的。執行結果如圖所示。
在這裡插入圖片描述
不同的顏色對應不同級別的輸出,我們可以將不同的結果合理劃分級別輸出,以更直觀方便地檢視除錯資訊。

6.3Request

最開始我們實現了 request 方法並且對 Headers 進行了修改。下面我們來看看 Request 還有哪些常用的功能。我們先用一個例項來感受一下。

from mitmproxy import ctx
def request(flow):
   request = flow.request
   info = ctx.log.info
   info(request.url)
   info(str(request.headers))
   info(str(request.cookies))
   info(request.host)
   info(request.method)
   info(str(request.port))
   info(request.scheme)

我們修改指令碼,然後在手機上開啟百度,即可看到 PC 端控制檯輸出了一系列的請求,在這裡我們找到第一個請求。控制檯列印輸出了 Request 的一些常見屬性,如 URL、Headers、Cookies、Host、Method、Scheme 等。輸出結果如圖所示。
在這裡插入圖片描述
結果中分別輸出了請求連結、請求頭、請求 Cookies、請求 Host、請求方法、請求埠、請求協議這些內容。

同時我們還可以對任意屬性進行修改,就像最初修改 Headers 一樣,直接賦值即可。例如,這裡將請求的 URL 修改一下,指令碼修改如下所示:

def request(flow):
   url = 'https://httpbin.org/get'
   flow.request.url = url

在這裡插入圖片描述
比較有意思的是,瀏覽器最上方還是呈現百度的 URL,但是頁面已經變成了 httpbin.org 的頁面了。另外,Cookies 明顯還是百度的 Cookies。我們只是用簡單的指令碼就成功把請求修改為其他的站點。通過這種方式修改和偽造請求就變得輕而易舉。

通過這個例項我們知道,有時候 URL 雖然是正確的,但是內容並非正確。我們需要進一步提高自己的安全防範意識。

Request 還有很多屬性,在此不再一一列舉。更多屬性可以參考:http://docs.mitmproxy.org/en/latest/scripting/api.html

只要我們瞭解了基本用法,會很容易地獲取和修改 Reqeust 的任意內容,比如可以用修改 Cookies、新增代理等方式來規避反爬。

6.4Response

對於爬蟲來說,我們更加關心的其實是響應的內容,因為 Response Body 才是爬取的結果。對於響應來說,mitmdump 也提供了對應的處理介面,就是 response 方法。下面我們用一個例項感受一下。

from mitmproxy import ctx
def response(flow):
   response = flow.response
   info = ctx.log.info
   info(str(response.status_code))
   info(str(response.headers))
   info(str(response.cookies))
   info(str(response.text))

將指令碼修改為如上內容,然後手機訪問:http://httpbin.org/get

這裡列印輸出了響應的 status_code、headers、cookies、text 這幾個屬性,其中最主要的 text 屬性就是網頁的原始碼。

PC 端控制檯輸出如圖所示。
在這裡插入圖片描述
控制檯輸出了響應的狀態碼、響應頭、Cookies、響應體這幾部分內容。

我們可以通過 response 方法獲取每個請求的響應內容。接下來再進行響應的資訊提取和儲存,我們就可以成功完成爬取了。

6.5示例操作

下面我們來介紹一個 App 的爬取實現,示例 App 可以參見附件。

通過 Charles 抓包之後我們可以發現,其介面的 URL 中包含了一個 token 引數,如圖所示。在這裡插入圖片描述
而且這個 token 每次請求都是變化的,我們目前也觀察不出它到底是怎麼構造的。

那麼我們如果想把抓包的這些結果儲存下來,應該怎麼辦呢?顯然就不好直接用程式來構造這些請求了,因為 token 的生成邏輯我們無從得知。這時候我們可以採取 mitmdump 實時處理的操作,只要能抓到包,那就可以把抓包的結果實時處理並儲存下來。

首先我們寫一個 mitmdump 指令碼,還是和原來一樣,儲存為 spider.py 內容如下:

from mitmproxy import ctx
def response(flow):
   response = flow.response
   info = ctx.log.info
   info(str(response.status_code))
   info(str(response.headers))
   info(str(response.cookies))
   info(str(response.text))

然後啟動一下指令碼,命令如下:

mitmdump -s spider.py

設定好手機代理為 mitmdump 的地址。
這時候開啟手機 App,會看到如下的介面,如圖所示。
在這裡插入圖片描述
這時候我們再返回控制檯,檢視下 mitmdump 的輸出,就會變成如下內容,如圖所示。在這裡插入圖片描述
我們可以看到這裡就輸出來了抓包後的結果,最後的這個內容實際上就是介面的返回結果,我們直接將其輸出到控制檯上了。

經過一些分析我們可以很輕鬆得知一次請求就會獲取 10 條電影的資料,我們可以對指令碼進行下改寫,對這個結果進行分析處理。比如我們可以遍歷每一條電影資料,然後將其儲存到本地,成為 JSON 檔案。

指令碼就可以改寫如下:

from mitmproxy import ctx
import json
def response(flow):
   response = flow.response
   if response.status_code != 200:
  return
   data = json.loads(str(response.text))
   for item in data.get('results'):
  name = item.get('name')
  with open(f'{name}.json', 'w', encoding='utf-8') as f:
  f.write(json.dumps(item, indent=2, ensure_ascii=False))

這裡我們首先對 response 的內容進行了解析,然後遍歷了 results 欄位的每個內容,然後獲取其 name 欄位當作檔案輸出的名稱,最後輸出儲存成一個 JSON 檔案。

這時候,我們再重新執行 mitmdump 和 App,就可以發現 mitmdump 的執行目錄下就出現了好多 JSON 檔案,這些 JSON 檔案的內容就是抓包的電影資料的結果,如圖所示。在這裡插入圖片描述
我們開啟其中一個結果,內容如下:

{
 "id": 3,
 "name": "肖申克的救贖",
 "alias": "The Shawshank Redemption",
 "cover": "https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c",
 "categories": [
   "劇情",
   "犯罪"
],
 "published_at": "1994-09-10",
 "minute": 142,
 "score": 9.5,
 "regions": [
   "美國"
]
}

可以看到這就是我們在 Charles 中看到的返回結果中的一條電影資料的內容,我們通過 mitmdump 對接實時處理指令碼實現了分析和儲存。

以上便是 mitmproxy 和 mitmdump 的基本用法。

相關文章