Mitmproxy/mitmdump中文文檔以及配置教程


Mitmproxy/mitmdump中文文檔以及配置教程

https://ptorch.com/news/269.html

https://ptorch.com/docs/10/mitmproxy_introduction

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

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

本文基於mitmproxy v5,當前版本號為 v5.0.1

1 Introduction

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

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

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

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

2 Features

  • 攔截HTTP和HTTPS請求和響應並即時修改它們
  • 保存完整的HTTP對話以供以之后重發和分析
  • 重發HTTP對話的客戶端
  • 重發先前記錄的服務的HTTP響應
  • 反向代理模式將流量轉發到指定的服務器
  • 在macOS和Linux上實現透明代理模式
  • 使用Python對HTTP流量進行腳本化修改
  • 實時生成用於攔截的SSL / TLS證書
  • And much, much more…

3 Installation

“安裝 mitmproxy”這句話是有歧義的,既可以指“安裝 mitmproxy 工具”,也可以指“安裝 python 的 mitmproxy 包”,注意后者是包含前者的。

如果只是拿 mitmproxy 做一個替代 fiddler 的工具,沒有什么定制化的需求,那完全只需要“安裝 mitmproxy 工具”即可,去 mitmproxy 官網 上下載一個 installer 便可開箱即用,不需要提前准備好 python 開發環境。但顯然,這不是這里要討論的,我們需要的是“安裝 python 的 mitmproxy 包”。

安裝 python 的 mitmproxy 包除了會得到 mitmproxy 工具外,還會得到開發定制腳本所需要的包依賴,其安裝過程並不復雜。

首先需要安裝好 python,版本需要不低於 3.6,且安裝了附帶的包管理工具 pip。這里不做展開,假設你已經准備好這樣的環境了。

安裝開始。

在 linux 中:

pip3.8 install mitmproxy

在 windows 中,以管理員身份運行 cmd 或 power shell:

pip3.8 install mitmproxy

在macos中:

brew install mitmproxy

安裝完成后,系統將擁有 mitmproxymitmdumpmitmweb 三個命令,由於 mitmproxy 命令不支持在 windows 系統中運行(這沒關系,不用擔心),我們可以拿 mitmdump 測試一下安裝是否成功,執行:

mitmdump --version

應當可以看到類似於這樣的輸出:

Mitmproxy: 5.0.1
Python:    3.8.2
OpenSSL:   OpenSSL 1.1.1f  31 Mar 2020
Platform:  macOS-10.15.3-x86_64-i386-64bit

4 Run

要啟動 mitmproxy 用 mitmproxymitmdumpmitmweb 這三個命令中的任意一個即可,這三個命令功能一致,且都可以加載自定義腳本,唯一的區別是交互界面的不同。

mitmproxy 命令啟動后,會提供一個命令行界面,用戶可以實時看到發生的請求,並通過命令過濾請求,查看請求數據。形如:

img

配置代理的方法與配置burpsuite一樣,我用的谷歌瀏覽器(127.0.0.1 8080),然后命令行執行

mitmproxy --listen-host 127.0.0.1 -p 8080

之后,瀏覽器地址訪問http://mitm.it/,點擊Other下載安裝證書,下圖所示:

img

img

usage: mitmproxy [options]
    #可選參數:

     -h, --help      show this help message and exit
     --version       show version number and exit
     --options       Show all options and their default values
     --commands      顯示所有命令及其簽名
     --set option[=value]    設置一個選項。 省略該值時,布爾值設置為true,字符串和整數設置為None(如果允許),並且序列為空。 布爾值可以為true,false或toggle
     -q, --quiet      Quiet.
     -v, --verbose     增加日志詳細程度
     --mode MODE, -m MODE   模式可以是“常規”,“透明”,“ socks5”,“反向:SPEC”或“上游:SPEC”。 對於反向和上游代理模式,SPEC是主機規范,形式為“ http [s]:// host [:port]”
     --no-anticache
     --anticache     去除可能導致服務器返回304-not-modified的請求頭
     --no-showhost
     --showhost      使用Host標頭構造用於顯示的URL
     --rfile PATH, -r PATH      從文件讀取流量
     --scripts SCRIPT, -s SCRIPT    執行腳本。 可能會多次通過
     --stickycookie FILTER      設置粘性Cookie過濾條件,根據要求匹配
     --stickyauth FILTER  設置粘性身份驗證過濾條件,根據要求匹配
     --save-stream-file PATH, -w PATH   流量到達時保存到文件(附加路徑)。      
     --no-anticomp
     --anticomp      嘗試令服務器向我們發送未壓縮的數據。
     --console-layout {horizontal,single,vertical}      控制台布局
     --no-console-layout-headers
     --console-layout-headers       顯示布局組件標題

    #代理選項:

     --listen-host HOST     綁定代理的地址到HOST
     --listen-port PORT, -p PORT    代理服務端口
     --no-server, -n
     --server      啟動代理服務器( 默認啟用)
     --ignore-hosts HOST        忽略主機並轉發所有流量,而不對其進行處理。 在透明模式下,建議使用IP地址(范圍),而不要使用主機名。 在常規模式下,僅SSL流量會被忽略,應使用主機名。 利用正則表達式解釋提供的值,並與ip或主機名匹配
     --allow-hosts HOST 與--ignore-hosts相反
     --tcp-hosts HOST   與--ignore-hosts相反。 對於與該模式匹配的所有主機,可以通過通用TCP SSL代理模式。 與--ignore相似,但是SSL連接被攔截。 通信內容以詳細模式打印到日志中
     --upstream-auth USER:PASS  通過將HTTP基本身份驗證添加到上游代理和反向代理請求。 格式:用戶名:密碼
     --proxyauth SPEC   需要代理身份驗證。 格式:“用戶名:密碼”,“任何”以接受任何用戶/密碼組合,“ @ path”以使用Apache htpasswd文件或用於LDAP認證的“ ldap [s]:url_server_ldap:dn_auth:password:dn_subtree”
     --no-rawtcp
     --rawtcp       啟用/禁用實驗性原始TCP支持。 以非ascii字節開頭的TCP連接將被視為與tcp_hosts匹配。 啟發式方法很粗糙,請謹慎使用。 默認禁用
     --no-http2
     --http2        啟用/禁用HTTP / 2支持。 默認情況下啟用HTTP / 2支持

    #SSL:

     --certs SPEC     形式為“ [domain =] path”的SSL證書。 該域可以包含通配符,如果未指定,則等於“ *”。 路徑中的文件是PEM格式的證書。 如果PEM中包含私鑰,則使用私鑰,否則使用conf目錄中的默認密鑰。 PEM文件應包含完整的證書鏈,並將葉子證書作為第一項
     --no-ssl-insecure
     --ssl-insecure, -k  不要驗證上游服務器SSL / TLS證書
     --key-size KEY_SIZE  證書和CA的TLS密鑰大小

    #客戶端重發:

     --client-replay PATH, -C PATH      重發來自已保存文件的客戶端請求

    #服務端重發:

     --server-replay PATH, -S PATH      從保存的文件重發服務器響應
     --no-server-replay-kill-extra
     --server-replay-kill-extra     在重發期間殺死額外的請求。  
     --no-server-replay-nopop
     --server-replay-nopop      使用后,請勿從服務器重發狀態中刪除流量。 這樣可以多次重發相同的響應。 
     --no-server-replay-refresh
     --server-replay-refresh        通過調整日期,到期和最后修改的header頭,以及調整cookie過期來刷新服務器重發響應。   

    #更換:

     --replacements PATTERN, -R PATTERN     替換形式:替換形式為``/ pattern / regex / replacement'',其中分隔符可以是任何字符。 可能會多次通過。

    #設置Headers:

     --setheaders PATTERN, -H PATTERN       格式為“ /pattern/header/value”的標題設置模式,其中分隔符可以是任何字符。

    #Filters:

    有關過濾條件表達式語法,請參見mitmproxy中的幫助。

     --intercept FILTER  設置攔截過濾表達式。
     --view-filter FILTER 將視圖限制為匹配流。

mitmdump是mitmproxy的命令行模式。 它提供了類似tcpdump的功能,可幫助你查看,記錄和以編程方式轉換HTTP流量。 有關完整的文檔,請參見--help

例1.

mitmdump -w outfile     #保存流量

以代理模式啟動mitmdump,並將所有流量寫入outfile中。

例2.

mitmdump -nr infile -w outfile "~m post"   #保存過濾后的流量

在不綁定代理端口(-n)的情況下啟動mitmdump,從infile中讀取所有流,應用指定的過濾表達式(僅匹配POST),然后寫入outfile。

例3.

mitmdump -nc outfile        #客戶端重發

在不綁定代理端口(-n)的情況下啟動mitmdump,然后重發outfile(-c filename)中的所有請求。 以較明顯的方式組合的標志,因此您可以重播來自一個文件的請求,並將結果流寫入另一個文件:

mitmdump -nc srcfile -w dstfile

有關更多信息,請參見client-side replay部分。

例4.

mitmdump -s examples/add_header.py      #運行一個腳本

這將運行add_header.py示例腳本,該腳本僅向所有響應添加新的header頭。

例5.

mitmdump -ns example/add_header.py -r srcfile -w dstfile        #腳本化數據轉換

此命令從srcfile加載數據請求,根據指定的腳本對其進行轉換,然后將其寫回到dstfile文件中。

mitmweb 命令啟動后,會提供一個 web 界面,用戶可以實時看到發生的請求,並通過 GUI 交互來過濾請求,查看請求數據。形如:

img

5 Scripts

5.1 編寫HTTP/1.1和HTTP/2.0腳本

完成了上述工作,我們已經具備了操作 mitmproxy 的基本能力 了。接下來開始開發自定義腳本,這才是 mitmproxy 真正強大的地方。

腳本的編寫需要遵循 mitmproxy 規定的套路,這樣的套路有兩個,使用時選其中一個套路即可。

第一個套路是,編寫一個 py 文件供 mitmproxy 加載,文件中定義了若干函數,這些函數實現了某些 mitmproxy 提供的事件,mitmproxy 會在某個事件發生時調用對應的函數,形如:

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 是個數組,每個元素是一個類實例,這些類有若干方法,這些方法實現了某些 mitmproxy 提供的事件,mitmproxy 會在某個事件發生時調用對應的方法。這些類,稱為一個個 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()
]
from mitmproxy import ctx


# 必須這么寫
def request(flow):
    print(flow.request.headers)
    ctx.log.info(str(flow.request.headers))
    ctx.log.warn(str(flow.request.headers))
    ctx.log.error(str(flow.request.headers))

    # http.HTTPFlow 實例 flow
    flow.request.headers  # 獲取所有頭信息,包含Host、User-Agent、Content-type等字段
    flow.request.url  # 完整的請求地址,包含域名及請求參數,但是不包含放在body里面的請求參數
    flow.request.pretty_url  # 同flow.request.url目前沒看出什么差別
    flow.request.host  # 域名
    flow.request.method  # 請求方式。POST、GET等
    flow.request.scheme  # 什么請求 ,如https
    flow.request.path  # 請求的路徑,url除域名之外的內容
    flow.request.get_text()  # 請求中body內容,有一些http會把請求參數放在body里面,那么可通過此方法獲取,返回字典類型
    flow.request.query  # 返回MultiDictView類型的數據,url直接帶的鍵值參數
    flow.request.get_content()  # bytes,結果如flow.request.get_text()
    flow.request.raw_content  # bytes,結果如flow.request.get_content()
    flow.request.urlencoded_form  # MultiDictView,content-type:application/x-www-form-urlencoded時的請求參數,不包含url直接帶的鍵值參數
    flow.request.multipart_form  #MultiDictView,content-type:multipart/form-data


def response(flow):
    flow.response.status_code  # 狀態碼
    flow.response.text  # 返回內容,已解碼
    flow.response.content  # 返回內容,二進制
    flow.response.setText()  # 修改返回內容,不需要轉碼

這里強烈建議使用第二種套路,直覺上就會感覺第二種套路更為先進,使用會更方便也更容易管理和拓展。況且這也是官方內置的一些 addon 的實現方式。

我們將上面第二種套路的示例代碼存為 addons.py,再重新啟動 mitmproxy:

mitmweb -s addons.py

當瀏覽器使用代理進行訪問時,就應該能看到控制台里有類似這樣的日志:

Web server listening at http://127.0.0.1:8081/
Loading script addons.py
Proxy server listening at http://*:8080
We've seen 1 flows
……
……
We've seen 2 flows
……
We've seen 3 flows
……
We've seen 4 flows
……
……
We've seen 5 flows
……

這就說明自定義腳本生效了。

5.2 腳本化WebSocket

在客戶端和服務器同意將連接升級到WebSocket之前,WebSocket協議最初看起來像是常規HTTP請求。初始HTTP握手的所有腳本事件以及專用的WebSocket事件都可以在此處找到

"""Process individual messages from a WebSocket connection."""
import re
from mitmproxy import ctx

def websocket_message(flow):
    # get the latest message
    message = flow.messages[-1]

    # was the message sent from the client or server?
    if message.from_client:
        ctx.log.info("Client sent a message: {}".format(message.content))
    else:
        ctx.log.info("Server sent a message: {}".format(message.content))

    # manipulate the message content
    message.content = re.sub(r'^Hello', 'HAPPY', message.content)

    if 'FOOBAR' in message.content:
        # kill the message and not send it to the other endpoint
        message.kill()

對於與WebSocket相關的對象,請查看websocket模塊以查找在編寫腳本時可以使用的所有屬性。

5.3 編寫TCP腳本

可以在此處找到有關TCP協議的所有事件。

"""
Process individual messages from a TCP connection.

This script replaces full occurences of "foo" with "bar" and prints various details for each message.
Please note that TCP is stream-based and *not* message-based. mitmproxy splits stream contents into "messages"
as they are received by socket.recv(). This is pretty arbitrary and should not be relied on.
However, it is sometimes good enough as a quick hack.

Example Invocation:

    mitmdump --rawtcp --tcp-hosts ".*" -s examples/tcp-simple.py
"""
from mitmproxy.utils import strutils
from mitmproxy import ctx
from mitmproxy import tcp

def tcp_message(flow: tcp.TCPFlow):
    message = flow.messages[-1]
    message.content = message.content.replace(b"foo", b"bar")

    ctx.log.info(
        f"tcp_message[from_client={message.from_client}), content={strutils.bytes_to_escaped_str(message.content)}]"
    )

對於與WebSocket相關的對象,請查看tcp模塊以查找在編寫腳本時可以使用的所有屬性。

6 Events

上述的腳本估計不用我解釋相信大家也看明白了,就是當 request 發生時,計數器加一,並打印日志。這里對應的是 request 事件,那總共有哪些事件呢?不多,也不少,這里詳細介紹一下。

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

1. 針對 HTTP 生命周期

def http_connect(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 收到了來自客戶端的 HTTP CONNECT 請求。在 flow 上設置非 2xx 響應將返回該響應並斷開連接。CONNECT 不是常用的 HTTP 請求方法,目的是與服務器建立代理連接,僅是 client 與 proxy 的之間的交流,所以 CONNECT 請求不會觸發 request、response 等其他常規的 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) 來自服務端的 HTTP 響應的頭部被成功讀取。此時 flow 中的 response 的 body 是空的。

def response(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 來自服務端端的 HTTP 響應被成功完整讀取。

def error(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 發生了一個 HTTP 錯誤。比如無效的服務端響應、連接斷開等。注意與“有效的 HTTP 錯誤返回”不是一回事,后者是一個正確的服務端響應,只是 HTTP code 表示錯誤而已。

2. 針對 TCP 生命周期

def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 建立了一個 TCP 連接。

def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 連接收到了一條消息,最近一條消息存於 flow.messages[-1]。消息是可修改的。

def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 發生了 TCP 錯誤。

def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 連接關閉。

3. 針對 Websocket 生命周期

def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 客戶端試圖建立一個 websocket 連接。可以通過控制 HTTP 頭部中針對 websocket 的條目來改變握手行為。flow 的 request 屬性保證是非空的的。

def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 建立了一個 websocket 連接。

def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 收到一條來自客戶端或服務端的 websocket 消息。最近一條消息存於 flow.messages[-1]。消息是可修改的。目前有兩種消息類型,對應 BINARY 類型的 frame 或 TEXT 類型的 frame。

def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 發生了 websocket 錯誤。

def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) websocket 連接關閉。

4. 針對網絡連接生命周期

def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客戶端連接到了 mitmproxy。注意一條連接可能對應多個 HTTP 請求。

def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客戶端斷開了和 mitmproxy 的連接。

def serverconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 連接到了服務端。注意一條連接可能對應多個 HTTP 請求。

def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 斷開了和服務端的連接。

def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 網絡 layer 發生切換。你可以通過返回一個新的 layer 對象來改變將被使用的 layer。詳見 layer 的定義

5. 通用生命周期

def configure(self, updated: typing.Set[str]):

(Called when) 配置發生變化。updated 參數是一個類似集合的對象,包含了所有變化了的選項。在 mitmproxy 啟動時,該事件也會觸發,且 updated 包含所有選項。

def done(self):

(Called when) addon 關閉或被移除,又或者 mitmproxy 本身關閉。由於會先等事件循環終止后再觸發該事件,所以這是一個 addon 可以看見的最后一個事件。由於此時 log 也已經關閉,所以此時調用 log 函數沒有任何輸出。

def load(self, entry: mitmproxy.addonmanager.Loader):

(Called when) addon 第一次加載時。entry 參數是一個 Loader 對象,包含有添加選項、命令的方法。這里是 addon 配置它自己的地方。

def log(self, entry: mitmproxy.log.LogEntry):

(Called when) 通過 mitmproxy.ctx.log 產生了一條新日志。小心不要在這個事件內打日志,否則會造成死循環。

def running(self):

(Called when) mitmproxy 完全啟動並開始運行。此時,mitmproxy 已經綁定了端口,所有的 addon 都被加載了。

def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):

(Called when) 一個或多個 flow 對象被修改了,通常是來自一個不同的 addon。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM