python mitmproxy 文檔

1豫柬、顧名思義告希,mitmproxy 就是用于 MITM 的 proxy,MITM 即[中間人攻擊]烧给,用于中間人攻擊的代理首先會向正常的代理一樣轉(zhuǎn)發(fā)請求暂雹,保障服務端與客戶端的通信,其次创夜,會適時的查杭跪、記錄其截獲的數(shù)據(jù),或篡改數(shù)據(jù)驰吓,引發(fā)服務端或客戶端特定的行為涧尿。
2、不同于 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)弯汰。
3、但 mitmproxy 并不會真的對無辜的人發(fā)起中間人攻擊湖雹,由于 mitmproxy 工作在 HTTP 層咏闪,而當前 HTTPS 的普及讓客戶端擁有了檢測并規(guī)避中間人攻擊的能力,所以要讓 mitmproxy 能夠正常工作摔吏,必須要讓客戶端(APP 或瀏覽器)主動信任 mitmproxy 的 SSL 證書鸽嫂,或忽略證書異常,這也就意味著 APP 或瀏覽器是屬于開發(fā)者本人的——顯而易見舔腾,這不是在做黑產(chǎn)溪胶,而是在做開發(fā)或測試搂擦。
4稳诚、那這樣的工具有什么實際意義呢?據(jù)我所知目前比較廣泛的應用是做仿真爬蟲瀑踢,即利用手機模擬器扳还、無頭瀏覽器來爬取 APP 或網(wǎng)站的數(shù)據(jù),mitmproxy 作為代理可以攔截橱夭、存儲爬蟲獲取到的數(shù)據(jù)氨距,或修改數(shù)據(jù)調(diào)整爬蟲的行為。
事實上棘劣,以上說的僅是 mitmproxy 以正向代理模式工作的情況俏让,通過調(diào)整配置,mitmproxy 還可以作為透明代理茬暇、反向代理首昔、上游代理、SOCKS 代理等糙俗,但這些工作模式針對 mitmproxy 來說似乎不大常用勒奇,故本文僅討論正向代理模式。
5巧骚、python腳本不要小于3.6
6赊颠、安裝完后,mitmdump 是命令行工具,mitmweb是一個web界面劈彪。


image.png

7竣蹦、第一個套路是,編寫一個 py 文件供 mitmproxy 加載沧奴,文件中定義了若干函數(shù)草添,這些函數(shù)實現(xiàn)了某些 mitmproxy 提供的事件,mitmproxy 會在某個事件發(fā)生時調(diào)用對應的函數(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)用對應的方法郑原。這些類,稱為一個個 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()
]

以上面的腳本啟動

mitmweb -s addons.py
8犯犁、事件針對不同生命周期分為 5 類∨鳎“生命周期”這里指在哪一個層面看待事件酸役,舉例來說,同樣是一次 web 請求驾胆,我可以理解為“HTTP 請求 -> HTTP 響應”的過程涣澡,也可以理解為“TCP 連接 -> TCP 通信 -> TCP 斷開”的過程。那么丧诺,如果我想拒絕來個某個 IP 的客戶端請求入桂,應當注冊函數(shù)到針對 TCP 生命周期 的 tcp_start 事件,又或者驳阎,我想阻斷對某個特定域名的請求時抗愁,則應當注冊函數(shù)到針對 HTTP 聲明周期的 http_connect 事件。其他情況同理
9呵晚、def http_connect(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 收到了來自客戶端的 HTTP CONNECT 請求蜘腌。在 flow 上設置非 2xx 響應將返回該響應并斷開連接。CONNECT 不是常用的 HTTP 請求方法劣纲,目的是與服務器建立代理連接逢捺,僅是 client 與 proxy 的之間的交流,所以 CONNECT 請求不會觸發(fā) request癞季、response 等其他常規(guī)的 HTTP 事件劫瞳。
10、def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求的頭部被成功讀取绷柒。此時 flow 中的 request 的 body 是空的志于。
11、def request(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自客戶端的 HTTP 請求被成功完整讀取废睦。
12伺绽、def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務端的 HTTP 響應的頭部被成功讀取。此時 flow 中的 response 的 body 是空的。
13奈应、def response(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 來自服務端端的 HTTP 響應被成功完整讀取澜掩。
14、def error(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 發(fā)生了一個 HTTP 錯誤杖挣。比如無效的服務端響應肩榕、連接斷開等。注意與“有效的 HTTP 錯誤返回”不是一回事惩妇,后者是一個正確的服務端響應株汉,只是 HTTP code 表示錯誤而已。
15歌殃、將百度搜索替換成360搜索:
def request(self, flow: mitmproxy.http.HTTPFlow):
# 忽略非百度搜索地址
if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
return

# 確認請求參數(shù)中有搜索詞
if "wd" not in flow.request.query.keys():
    ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
    return

# 輸出原始的搜索詞
ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
# 替換搜索詞為“360搜索”
flow.request.query.set_all("wd", ["360搜索"])

16乔妈、360搜索改成谷歌搜索
def response(self, flow: mitmproxy.http.HTTPFlow):
# 忽略非 360 搜索地址
if flow.request.host != "www.so.com":
return

# 將響應中所有“搜索”替換為“請使用谷歌”
text = flow.response.get_text()
text = text.replace("搜索", "請使用谷歌")
flow.response.set_text(text)

17、如果客戶想訪問谷歌氓皱,拒絕:
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
# 確認客戶端是想訪問 www.google.com
if flow.request.host == "www.google.com":
# 返回一個非 2xx 響應斷開連接
flow.response = http.HTTPResponse.make(404)
18路召、整合:
import mitmproxy.http
from mitmproxy import ctx, http

class Joker:
def request(self, flow: mitmproxy.http.HTTPFlow):
if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
return

    if "wd" not in flow.request.query.keys():
        ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
        return

    ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
    flow.request.query.set_all("wd", ["360搜索"])

def response(self, flow: mitmproxy.http.HTTPFlow):
    if flow.request.host != "www.so.com":
        return

    text = flow.response.get_text()
    text = text.replace("搜索", "請使用谷歌")
    flow.response.set_text(text)

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
    if flow.request.host == "www.google.com":
        flow.response = http.HTTPResponse.make(404)

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)

import counter
import joker

addons = [
counter.Counter(),
joker.Joker(),
]

mitmweb -s addons.py

18、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 連接關閉抡医。
19躲因、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) 發(fā)生了 websocket 錯誤宙刘。
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
(Called when) websocket 連接關閉苍姜。
20、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) 網(wǎng)絡 layer 發(fā)生切換。你可以通過返回一個新的 layer 對象來改變將被使用的 layer棵譬。
21铁追、
def configure(self, updated: typing.Set[str]):
(Called when) 配置發(fā)生變化。updated 參數(shù)是一個類似集合的對象茫船,包含了所有變化了的選項琅束。在 mitmproxy 啟動時,該事件也會觸發(fā),且 updated 包含所有選項。
def done(self):
(Called when) addon 關閉或被移除培愁,又或者 mitmproxy 本身關閉培廓。由于會先等事件循環(huán)終止后再觸發(fā)該事件,所以這是一個 addon 可以看見的最后一個事件疗我。由于此時 log 也已經(jīng)關閉,所以此時調(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。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末空骚,一起剝皮案震驚了整個濱河市纺讲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌府怯,老刑警劉巖刻诊,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牺丙,居然都是意外死亡则涯,警方通過查閱死者的電腦和手機复局,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粟判,“玉大人亿昏,你說我怎么就攤上這事〉到福” “怎么了角钩?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呻澜。 經(jīng)常有香客問我递礼,道長,這世上最難降的妖魔是什么羹幸? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任脊髓,我火速辦了婚禮,結(jié)果婚禮上栅受,老公的妹妹穿的比我還像新娘将硝。我一直安慰自己,他們只是感情好屏镊,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布依疼。 她就那樣靜靜地躺著,像睡著了一般而芥。 火紅的嫁衣襯著肌膚如雪律罢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天蔚出,我揣著相機與錄音弟翘,去河邊找鬼虫腋。 笑死骄酗,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的悦冀。 我是一名探鬼主播趋翻,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盒蟆!你這毒婦竟也來了踏烙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤历等,失蹤者是張志新(化名)和其女友劉穎讨惩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寒屯,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡荐捻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年黍少,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片处面。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡厂置,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出魂角,到底是詐尸還是另有隱情昵济,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布野揪,位于F島的核電站访忿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏斯稳。R本人自食惡果不足惜醉顽,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望平挑。 院中可真熱鬧游添,春花似錦、人聲如沸通熄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唇辨。三九已至廊酣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赏枚,已是汗流浹背亡驰。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饿幅,地道東北人凡辱。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像栗恩,于是被迫代替她去往敵國和親透乾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 使用selenium模擬瀏覽器進行數(shù)據(jù)抓取無疑是當下最通用的數(shù)據(jù)采集方案磕秤,它通吃各種數(shù)據(jù)加載方式乳乌,能夠繞過客戶JS...
    warmi_閱讀 13,421評論 5 7
  • 網(wǎng)絡編程 一.楔子 你現(xiàn)在已經(jīng)學會了寫python代碼汉操,假如你寫了兩個python文件a.py和b.py,分別去運...
    go以恒閱讀 1,995評論 0 6
  • 英文文檔蒙兰,一開始我也是抗拒的磷瘤,邊翻譯邊看其弊,也就花費了1個小時基本就閱讀過了,我的英文基礎其實很差膀斋。附上鏈接:鏈接:...
    lonecolonel閱讀 9,865評論 3 1
  • 摘要:mitmproxy是一個支持HTTP和HTTPS的抓包程序梭伐,有類似Fiddler、Charles的功能仰担,只不...
    朝畫夕拾閱讀 1,992評論 0 2
  • 今晚看完丹麥一部電影(狩獵)糊识,此片從最淺顯的方面來說,是關于一個幼兒園女孩和一名男教師之間的故事摔蓝,但從更深層次來看...
    一半_MY閱讀 460評論 0 0