Mitmproxy教程
本文是一個較為完整的 mitmproxy教程,側(cè)重于介紹如何開發(fā)攔截腳本,幫助讀者能夠快速得到一個自定義的代理工具。
本文假設(shè)讀者有基本的 python 知識,且已經(jīng)安裝好了一個 python 3 開發(fā)環(huán)境。如果你對 nodejs 的熟悉程度大于對 python,可移步到 anyproxy,anyproxy 的功能與 mitmproxy 基本一致,但使用 js 編寫定制腳本。除此之外我就不知道有什么其他類似的工具了,如果你知道,歡迎評論告訴我。
本文基于 mitmproxy v5,當(dāng)前版本號為 v5.0.1。
Introduction
顧名思義,mitmproxy 就是用于 MITM 的 proxy,MITM 即中間人攻擊(Man-in-the-middle attack)。用于中間人攻擊的代理首先會向正常的代理一樣轉(zhuǎn)發(fā)請求,保障服務(wù)端與客戶端的通信,其次,會適時的查、記錄其截獲的數(shù)據(jù),或篡改數(shù)據(jù),引發(fā)服務(wù)端或客戶端特定的行為。
不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不僅可以截獲請求幫助開發(fā)者查看、分析,更可以通過自定義腳本進行二次開發(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ā)或測試。
事實上,以上說的僅是 mitmproxy 以正向代理模式工作的情況,通過調(diào)整配置,mitmproxy 還可以作為透明代理、反向代理、上游代理、SOCKS 代理等,但這些工作模式針對 mitmproxy 來說似乎不大常用,故本文僅討論正向代理模式。
Features
- 攔截HTTP和HTTPS請求和響應(yīng)并即時修改它們
- 保存完整的HTTP對話以供以之后重發(fā)和分析
- 重發(fā)HTTP對話的客戶端
- 重發(fā)先前記錄的服務(wù)的HTTP響應(yīng)
- 反向代理模式將流量轉(zhuǎn)發(fā)到指定的服務(wù)器
- 在macOS和Linux上實現(xiàn)透明代理模式
- 使用Python對HTTP流量進行腳本化修改
- 實時生成用于攔截的SSL / TLS證書
- And much, much more…
Installation
“安裝 mitmproxy”這句話是有歧義的,既可以指“安裝 mitmproxy 工具”,也可以指“安裝 python 的 mitmproxy 包”,注意后者是包含前者的。
如果只是拿 mitmproxy 做一個替代 fiddler 的工具,沒有什么定制化的需求,那完全只需要“安裝 mitmproxy 工具”即可,去 mitmproxy 官網(wǎng) 上下載一個 installer 便可開箱即用,不需要提前準(zhǔn)備好 python 開發(fā)環(huán)境。但顯然,這不是這里要討論的,我們需要的是“安裝 python 的 mitmproxy 包”。
安裝 python 的 mitmproxy 包除了會得到 mitmproxy 工具外,還會得到開發(fā)定制腳本所需要的包依賴,其安裝過程并不復(fù)雜。
首先需要安裝好 python,版本需要不低于 3.6,且安裝了附帶的包管理工具 pip。這里不做展開,假設(shè)你已經(jīng)準(zhǔn)備好這樣的環(huán)境了。
安裝開始。
在 linux 中:
sudo pip3 install mitmproxy
在 windows 中,以管理員身份運行 cmd 或 power shell:
pip3 install mitmproxy
在macos中:
brew install mitmproxy
安裝完成后,系統(tǒng)將擁有 mitmproxy、mitmdump、mitmweb 三個命令,由于 mitmproxy 命令不支持在 windows 系統(tǒng)中運行(這沒關(guān)系,不用擔(dān)心),我們可以拿 mitmdump 測試一下安裝是否成功,執(zhí)行:
mitmdump --version
應(yīng)當(dāng)可以看到類似于這樣的輸出:
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
Run
要啟動 mitmproxy 用 mitmproxy、mitmdump、mitmweb 這三個命令中的任意一個即可,這三個命令功能一致,且都可以加載自定義腳本,唯一的區(qū)別是交互界面的不同。
mitmproxy 命令啟動后,會提供一個命令行界面,用戶可以實時看到發(fā)生的請求,并通過命令過濾請求,查看請求數(shù)據(jù)。形如:
usage: mitmproxy [options]
#可選參數(shù):
-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] 設(shè)置一個選項。 省略該值時,布爾值設(shè)置為true,字符串和整數(shù)設(shè)置為None(如果允許),并且序列為空。 布爾值可以為true,false或toggle
-q, --quiet Quiet.
-v, --verbose 增加日志詳細程度
--mode MODE, -m MODE 模式可以是“常規(guī)”,“透明”,“ socks5”,“反向:SPEC”或“上游:SPEC”。 對于反向和上游代理模式,SPEC是主機規(guī)范,形式為“ http [s]:// host [:port]”
--no-anticache
--anticache 去除可能導(dǎo)致服務(wù)器返回304-not-modified的請求頭
--no-showhost
--showhost 使用Host標(biāo)頭構(gòu)造用于顯示的URL
--rfile PATH, -r PATH 從文件讀取流量
--scripts SCRIPT, -s SCRIPT 執(zhí)行腳本。 可能會多次通過
--stickycookie FILTER 設(shè)置粘性Cookie過濾條件,根據(jù)要求匹配
--stickyauth FILTER 設(shè)置粘性身份驗證過濾條件,根據(jù)要求匹配
--save-stream-file PATH, -w PATH 流量到達時保存到文件(附加路徑)。
--no-anticomp
--anticomp 嘗試令服務(wù)器向我們發(fā)送未壓縮的數(shù)據(jù)。
--console-layout {horizontal,single,vertical} 控制臺布局
--no-console-layout-headers
--console-layout-headers 顯示布局組件標(biāo)題
#代理選項:
--listen-host HOST 綁定代理的地址到HOST
--listen-port PORT, -p PORT 代理服務(wù)端口
--no-server, -n
--server 啟動代理服務(wù)器( 默認啟用)
--ignore-hosts HOST 忽略主機并轉(zhuǎn)發(fā)所有流量,而不對其進行處理。 在透明模式下,建議使用IP地址(范圍),而不要使用主機名。 在常規(guī)模式下,僅SSL流量會被忽略,應(yīng)使用主機名。 利用正則表達式解釋提供的值,并與ip或主機名匹配
--allow-hosts HOST 與--ignore-hosts相反
--tcp-hosts HOST 與--ignore-hosts相反。 對于與該模式匹配的所有主機,可以通過通用TCP SSL代理模式。 與--ignore相似,但是SSL連接被攔截。 通信內(nèi)容以詳細模式打印到日志中
--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字節(jié)開頭的TCP連接將被視為與tcp_hosts匹配。 啟發(fā)式方法很粗糙,請謹(jǐn)慎使用。 默認禁用
--no-http2
--http2 啟用/禁用HTTP / 2支持。 默認情況下啟用HTTP / 2支持
#SSL:
--certs SPEC 形式為“ [domain =] path”的SSL證書。 該域可以包含通配符,如果未指定,則等于“ *”。 路徑中的文件是PEM格式的證書。 如果PEM中包含私鑰,則使用私鑰,否則使用conf目錄中的默認密鑰。 PEM文件應(yīng)包含完整的證書鏈,并將葉子證書作為第一項
--no-ssl-insecure
--ssl-insecure, -k 不要驗證上游服務(wù)器SSL / TLS證書
--key-size KEY_SIZE 證書和CA的TLS密鑰大小
#客戶端重發(fā):
--client-replay PATH, -C PATH 重發(fā)來自已保存文件的客戶端請求
#服務(wù)端重發(fā):
--server-replay PATH, -S PATH 從保存的文件重發(fā)服務(wù)器響應(yīng)
--no-server-replay-kill-extra
--server-replay-kill-extra 在重發(fā)期間殺死額外的請求。
--no-server-replay-nopop
--server-replay-nopop 使用后,請勿從服務(wù)器重發(fā)狀態(tài)中刪除流量。 這樣可以多次重發(fā)相同的響應(yīng)。
--no-server-replay-refresh
--server-replay-refresh 通過調(diào)整日期,到期和最后修改的header頭,以及調(diào)整cookie過期來刷新服務(wù)器重發(fā)響應(yīng)。
#更換:
--replacements PATTERN, -R PATTERN 替換形式:替換形式為``/ pattern / regex / replacement'',其中分隔符可以是任何字符。 可能會多次通過。
#設(shè)置Headers:
--setheaders PATTERN, -H PATTERN 格式為“ /pattern/header/value”的標(biāo)題設(shè)置模式,其中分隔符可以是任何字符。
#Filters:
有關(guān)過濾條件表達式語法,請參見mitmproxy中的幫助。
--intercept FILTER 設(shè)置攔截過濾表達式。
--view-filter FILTER 將視圖限制為匹配流。
mitmdump是mitmproxy的命令行模式。 它提供了類似tcpdump的功能,可幫助你查看,記錄和以編程方式轉(zhuǎn)換HTTP流量。 有關(guān)完整的文檔,請參見--help。
例1.
mitmdump -w outfile #保存流量
以代理模式啟動mitmdump,并將所有流量寫入outfile中。
例2.
mitmdump -nr infile -w outfile "~m post" #保存過濾后的流量
在不綁定代理端口(-n)的情況下啟動mitmdump,從infile中讀取所有流,應(yīng)用指定的過濾表達式(僅匹配POST),然后寫入outfile。
例3.
mitmdump -nc outfile #客戶端重發(fā)
在不綁定代理端口(-n)的情況下啟動mitmdump,然后重發(fā)outfile(-c filename)中的所有請求。 以較明顯的方式組合的標(biāo)志,因此您可以重播來自一個文件的請求,并將結(jié)果流寫入另一個文件:
mitmdump -nc srcfile -w dstfile
有關(guān)更多信息,請參見client-side replay部分。
例4.
mitmdump -s examples/add_header.py #運行一個腳本
這將運行add_header.py示例腳本,該腳本僅向所有響應(yīng)添加新的header頭。
例5.
mitmdump -ns example/add_header.py -r srcfile -w dstfile #腳本化數(shù)據(jù)轉(zhuǎn)換
此命令從srcfile加載數(shù)據(jù)請求,根據(jù)指定的腳本對其進行轉(zhuǎn)換,然后將其寫回到dstfile文件中。
mitmweb 命令啟動后,會提供一個 web 界面,用戶可以實時看到發(fā)生的請求,并通過 GUI 交互來過濾請求,查看請求數(shù)據(jù)。形如:
Scripts
完成了上述工作,我們已經(jīng)具備了操作 mitmproxy 的基本能力 了。接下來開始開發(fā)自定義腳本,這才是 mitmproxy 真正強大的地方。
腳本的編寫需要遵循 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()
]
這里強烈建議使用第二種套路,直覺上就會感覺第二種套路更為先進,使用會更方便也更容易管理和拓展。況且這也是官方內(nèi)置的一些 addon 的實現(xiàn)方式。
我們將上面第二種套路的示例代碼存為 addons.py,再重新啟動 mitmproxy:
mitmweb -s addons.py
當(dāng)瀏覽器使用代理進行訪問時,就應(yīng)該能看到控制臺里有類似這樣的日志:
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
……
這就說明自定義腳本生效了。
Events
上述的腳本估計不用我解釋相信大家也看明白了,就是當(dāng) request 發(fā)生時,計數(shù)器加一,并打印日志。這里對應(yīng)的是 request 事件,那總共有哪些事件呢?不多,也不少,這里詳細介紹一下。
事件針對不同生命周期分為 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 事件。其他情況同理。
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 表示錯誤而已。
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) 發(fā)生了 TCP 錯誤。
def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
(Called when) TCP 連接關(guān)閉。
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) 收到一條來自客戶端或服務(wù)端的 websocket 消息。最近一條消息存于 flow.messages[-1]。消息是可修改的。目前有兩種消息類型,對應(yīng) BINARY 類型的 frame 或 TEXT 類型的 frame。
def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
(Called when) 發(fā)生了 websocket 錯誤。
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
(Called when) websocket 連接關(guān)閉。
4. 針對網(wǎng)絡(luò)連接生命周期
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
(Called when) 客戶端連接到了 mitmproxy。注意一條連接可能對應(yīng)多個 HTTP 請求。
def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
(Called when) 客戶端斷開了和 mitmproxy 的連接。
def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
(Called when) mitmproxy 連接到了服務(wù)端。注意一條連接可能對應(yīng)多個 HTTP 請求。
def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
(Called when) mitmproxy 斷開了和服務(wù)端的連接。
def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
(Called when) 網(wǎng)絡(luò) layer 發(fā)生切換。你可以通過返回一個新的 layer 對象來改變將被使用的 layer。詳見 layer 的定義。
5. 通用生命周期
def configure(self, updated: typing.Set[str]):
(Called when) 配置發(fā)生變化。updated 參數(shù)是一個類似集合的對象,包含了所有變化了的選項。在 mitmproxy 啟動時,該事件也會觸發(fā),且 updated 包含所有選項。
def done(self):
(Called when) addon 關(guān)閉或被移除,又或者 mitmproxy 本身關(guān)閉。由于會先等事件循環(huán)終止后再觸發(fā)該事件,所以這是一個 addon 可以看見的最后一個事件。由于此時 log 也已經(jīng)關(guān)閉,所以此時調(diào)用 log 函數(shù)沒有任何輸出。
def load(self, entry: mitmproxy.addonmanager.Loader):
(Called when) addon 第一次加載時。entry 參數(shù)是一個 Loader 對象,包含有添加選項、命令的方法。這里是 addon 配置它自己的地方。
def log(self, entry: mitmproxy.log.LogEntry):
(Called when) 通過 mitmproxy.ctx.log 產(chǎn)生了一條新日志。小心不要在這個事件內(nèi)打日志,否則會造成死循環(huán)。
def running(self):
(Called when) mitmproxy 完全啟動并開始運行。此時,mitmproxy 已經(jīng)綁定了端口,所有的 addon 都被加載了。
def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):
(Called when) 一個或多個 flow 對象被修改了,通常是來自一個不同的 addon。
Reference:
- mitmproxy 官方文檔:https://docs.mitmproxy.org/stable
- mitmproxy 腳本示例:https://github.com/mitmproxy/mitmproxy/tree/master/examples
總結(jié)
以上是生活随笔為你收集整理的Mitmproxy教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#语言类型
- 下一篇: STM32F103ZET6独立看门狗