mitmproxy 使用
powershell無法啟動(dòng)虛擬環(huán)境
0. 準(zhǔn)備工作
參考文檔:
什么是mitmproxy茴晋?
mitm
為Man-In-The-Middle attack
悉患;mitmproxy
即為 中間人攻擊代理。
為什么要用mitmproxy涨缚?相比Fiddler 和 Charles它有什么優(yōu)勢黍檩?
-
mitmproxy
不僅可以截獲請(qǐng)求幫助開發(fā)者查看、分析,更可以通過自定義腳本進(jìn)行二次開發(fā)。舉例來說失乾,利用Fiddler
可以過濾出瀏覽器對(duì)某個(gè)特定url
的請(qǐng)求,并查看谋币、分析其數(shù)據(jù)仗扬,但實(shí)現(xiàn)不了高度定制化的需求症概,類似于:“截獲對(duì)瀏覽器對(duì)該url
的請(qǐng)求蕾额,將返回內(nèi)容置空,并將真實(shí)的返回內(nèi)容存到某個(gè)數(shù)據(jù)庫彼城,出現(xiàn)異常時(shí)發(fā)出郵件通知”诅蝶。而對(duì)于mitmproxy
退个,這樣的需求可以通過載入自定義python
腳本輕松實(shí)現(xiàn)。
特征
- 攔截HTTP和HTTPS請(qǐng)求和響應(yīng)并即時(shí)修改它們
- 保存完整的HTTP對(duì)話以供以后重播和分析
- 重播HTTP對(duì)話的客戶端
- 重播先前記錄的服務(wù)器的HTTP響應(yīng)
- 反向代理模式將流量轉(zhuǎn)發(fā)到指定的服務(wù)器
- macOS和Linux上的透明代理模式
- 使用Python對(duì)HTTP流量進(jìn)行腳本化更改
- 實(shí)時(shí)生成用于攔截的SSL / TLS證書
- 還有更多……
1. 安裝
1.1 模塊安裝
安裝:
pip install mitmproxy
查看安裝成功與否:
-
cmd
窗口调炬,查看版本
mitmdump --version
出現(xiàn)以下字眼语盈,則是成功安裝了。
Mitmproxy: 5.2
Python: 3.7.6
OpenSSL: OpenSSL 1.1.1g 21 Apr 2020
Platform: Windows-10-10.0.18362-SP0
1.2 證書安裝
模塊安裝完成后缰泡,首次運(yùn)行 mitmproxy
或 mitmdump
刀荒,在當(dāng)前用戶下面會(huì)生成幾個(gè)ca證書。
從Windows
用戶界面的 .mitmproxy
中棘钞,點(diǎn)擊進(jìn)去缠借,可以看到有多個(gè)證書,
證書 | 作用 |
---|---|
mitmproxy-ca.pem | PEM格式的證書和私鑰宜猜。 |
mitmproxy-ca-cert.pem | PEM格式的證書泼返。使用它可以在大多數(shù)非Windows平臺(tái)上分發(fā)。 |
mitmproxy-ca-cert.p12 | PKCS12格式的證書姨拥。適用于Windows(安裝這個(gè) |
mitmproxy-ca-cert.cer | 與.pem相同的文件绅喉,但某些Android設(shè)備需要擴(kuò)展名。 |
Windows
端:
mitmproxy-ca-cert.p12
手機(jī)
端:
- 配置好wifi連接之后叫乌,訪問
mitm.it
- 下載對(duì)應(yīng)手機(jī)系統(tǒng)的證書柴罐,然后安裝即可。
抓包示例:
-
Windows
端- 要使用代理 + 走指定的端口哦W劢妗@鲂!
-
手機(jī)
端- 配置
Windows
端的ip + 指定代理0蛎辍屠阻!
- 配置
2. 組件
當(dāng)我們談?wù)摗?mitmproxy
“時(shí),我們通常指這三種工具中的任何一種--它們只是同一核心代理的不同前端额各。
Tools | Description |
---|---|
mitmproxy | 供交互式界面(Windows 系統(tǒng)不可用 |
mitmdump | 提供簡單明了的終端輸出 |
mitmweb | 提供基于瀏覽器的圖形界面 |
正常使用用mitmdump
就足夠了国觉。
所以后面的案例也是使用 mitmdump
去做展示。
mitmproxy
默認(rèn)綁定的端口為 127.0.0.1:8080
注意一下:
- 如果端口被占用了虾啦,會(huì)提示報(bào)錯(cuò)哦麻诀!
2.1 mitmproxy
Windows
系統(tǒng)不可用,這里暫不展示傲醉。
2.2 mitmdump
查看所有命令:
mitmdump --help
查看版本:
mitmdump --version
常用命令:
-p 8888 # 指定端口
-s xxx.py # 執(zhí)行指定腳本
-w outfile # 指定輸出文件
-q quiet # 僅匹配腳本過濾后的數(shù)據(jù)包
"~m post" # 僅匹配Post請(qǐng)求
帶有顏色的print:
-
log
蝇闭,帶有輸出不同顏色的功能(個(gè)人覺得沒有什么用- info 白色
- warn 黃色
- error 紅色
注意這里要使用cmd,使用PowerShell顯示出來的顏色效果不完整硬毕。
mitmDemoOne.py
class Demo:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""Print different colors"""
url = flow.request.url
if 'sunrise' in url:
print(type(url))
ctx.log.info('Color White:' + url)
ctx.log.warn('Color Yellow:' + url)
ctx.log.error('Color Red:' + url)
addons = [
Demo()
]
2.3 mitmweb
監(jiān)聽的端口是 127.0.0.1:8080
呻引,
同時(shí)提供一個(gè) web
交互界面在 127.0.0.1:8081
。
用 百度一下 示例吐咳。
介紹:
-
攔截
- 修改請(qǐng)求前數(shù)據(jù)
- 修改請(qǐng)求后數(shù)據(jù)
篩選
高亮
重放請(qǐng)求
3. 簡單使用示例
3.1 使用示例
正常使用用mitmdump
就足夠了逻悠。所以這里主要用 mitmdump
來做一個(gè)展示元践,
后面的案例也是使用 mitmdump
去做展示。
基本操作的話童谒,那只看 常規(guī)代理
方式就可以了单旁;
操作模式:https://docs.mitmproxy.org/stable/concepts-modes/
腳本編寫:https://docs.mitmproxy.org/stable/addons-scripting/
如何工作:https://docs.mitmproxy.org/stable/concepts-howmitmproxyworks/
測試網(wǎng)站:
常用的兩個(gè)函數(shù)簡單演示:
這里介紹一下使用的比較多的兩個(gè)函數(shù),其他的可以通過官方文檔去進(jìn)行一個(gè)系統(tǒng)的學(xué)習(xí)饥伊。
def request(flow):
pass
def response(flow):
pass
- request
common | Description |
---|---|
request = flow.request |
|
request.url |
url |
request.host |
域名 |
request.headers |
請(qǐng)求頭 |
request.method |
方式:POST象浑、GET等 |
request.scheme |
類型:http、https |
request.path |
路徑琅豆,URL除域名之外的內(nèi)容 |
request.query |
返回MultiDictView類型的數(shù)據(jù)融柬,URL的鍵值參數(shù) |
request.query.keys() |
獲取所有請(qǐng)求參數(shù)鍵值的鍵 |
request.query.values() |
獲取所有請(qǐng)求參數(shù)鍵值的值 |
request.query.get('wd') |
獲取請(qǐng)求參數(shù)中wd 鍵的值(前提是要有 wb 參數(shù) |
request.query.set_all('wd', ['python']) |
將wd 參數(shù)的值修改為 python
|
修改請(qǐng)求頭:
mitmDemoTwo.py
flow.request.headers['User-Agent'] = 'Mozilla/5.0'
將百度搜索修改為python:
mitmDemoThree.py
def request(flow):
if 'https://www.baidu.com' in flow.request.url:
# 取得請(qǐng)求參數(shù)wd的值
print(flow.request.query.get('wd'))
# 獲取所有請(qǐng)求參數(shù)鍵值的鍵
print(list(flow.request.query.keys()))
# 獲取所有請(qǐng)求參數(shù)鍵值的值
print(list(flow.request.query.values()))
# 修改請(qǐng)求參數(shù)
flow.request.query.set_all('wd',['python'])
# 打印修改過后的參數(shù)
print(flow.request.query.get('wd'))
- response
common | Description |
---|---|
response = flow.response |
|
response.status_code |
響應(yīng)碼 |
response.text |
文本(同下) |
response.content |
Bytes類型 |
response.headers |
響應(yīng)頭 |
response.cookies |
響應(yīng)cookie |
response.set_text() |
修改響應(yīng)的文本 |
response.get_text() |
文本(同上) |
flow.response= flow.response.make(404) |
響應(yīng)404 |
修改文本
mitmDemoFour.py
flow.response.set_text(text)
拒絕響應(yīng)
mitmDemoFour.py
# 同下
flow.response = mitmproxy.http.HTTPResponse.make(401)
# 同下
flow.response= flow.response.make(404)
拒絕響應(yīng):在百度搜索 十八禁
mitmDemoFive.py
if flow.request.query.get('wd') == '十八禁':
flow.response = mitmproxy.http.HTTPResponse.make(
404, # (optional) status code
b"You son of a bitch, Please leave.", # (optional) content
{"Content-Type": "text/html"} # (optional) headers
)
3.1.1 簡單應(yīng)用
需求:
1. 修改請(qǐng)求(如果是搜索雷鋒,則修改為 是小菜一碟吖
2. 修改響應(yīng)(將頁面所有 Python 字眼 替換為 是小菜一碟吖
3. 如果存在少兒不宜字眼(例:十八禁趋距、迷藥等)粒氧,則拒絕響應(yīng),引導(dǎo)他向好
代碼:
mitmDemoSix.py
import mitmproxy.http
from mitmproxy import ctx
class Demo:
def request(self, flow: mitmproxy.http.HTTPFlow):
"""Do somethings"""
request = flow.request
if 'https://www.baidu.com/' in request.url:
keyword = request.query.get('wd')
filter_words = ['迷藥', '十八禁']
if keyword == '雷鋒':
# 修改請(qǐng)求參數(shù)
flow.request.query.set_all('wd', ['是小菜一碟吖'])
if keyword in filter_words:
flow.response = mitmproxy.http.HTTPResponse.make(
status_code=400,
content=''' <title>娘希匹=诟M舛ⅰ!</title>
<h1>警告R砣浮1ス丁!即將查水表</h1>
<h2>望你善良狼渊,愿你向上</h2>
<a>點(diǎn)擊跳轉(zhuǎn):</a>
<a target="_blank">是小菜一碟吖的學(xué)習(xí)頻道</a>
''',
# content="你可拉倒吧O浒尽!狈邑!查詢的什么娘希匹玩意兒3切搿!米苹!"
headers={"Content-Type": "text/html"}
)
def response(self, flow: mitmproxy.http.HTTPFlow):
"""Do somethings"""
response = flow.response
if flow.request.host == 'www.baidu.com':
replace_words = ['你好', 'python', 'Python']
text = response.get_text()
text = list(map(lambda x: text.replace(x, '是小菜一碟吖'), replace_words))[0]
flow.response.set_text(text=text)
addons = [
Demo()
]
當(dāng)然糕伐,這里超綱了,也就是覺得有趣蘸嘶,就拉出來講一講良瞧。
3.2 報(bào)錯(cuò)解決
502 Bad Gateway
Certificate verification error for xxx: unable to get local issuer certificate (errno: 20, depth: 0)
網(wǎng)關(guān)證書驗(yàn)證錯(cuò)誤,解決方法有二:
- 執(zhí)行--ssl-insecure
- 下載最新的cacert.pem替換(
Python安裝路徑\Lib\site-packages\certifi
)的目錄證書
4. 案例展示
4.1 mitmproxy + Selenium
電腦端自動(dòng)化爬蟲
案例說明:
- Selenium 自動(dòng)翻頁训唱,
- mitmproxy 進(jìn)行信息采集褥蚯,
- 在指定網(wǎng)站,輸入 指定關(guān)鍵詞 以及 爬取的頁碼數(shù)量况增,即可赞庶。
注意點(diǎn):
- 評(píng)論數(shù)量是另外一個(gè)文件,需要另外進(jìn)行解析。
- 返回評(píng)論適量的鏈接有兩個(gè)尘执,要區(qū)別做判斷。
selenium JD:
"""輸入關(guān)鍵詞 + 頁碼數(shù)量 Jd自動(dòng)翻頁程序"""
import time
from selenium import webdriver
class JdSpider:
"""OK"""
def __init__(self, keyword=None, page=None):
self.url = 'https://www.jd.com/'
self.browser = None
self.page = int(page)
self.keyword = keyword
def __del__(self):
self.browser.close()
def open_browser(self):
"""打開瀏覽器"""
self.browser = webdriver.Chrome()
# self.browser.maximize_window()
self.browser.set_window_size(1350, 850)
def search_keyword(self):
'''搜索關(guān)鍵字'''
self.browser.get(self.url)
# 輸入內(nèi)容
self.browser.find_element_by_xpath('//*[@id="key"]').send_keys(self.keyword)
# 模擬點(diǎn)擊
self.browser.find_element_by_xpath('//*[@id="search"]/div/div[2]/button').click()
def turn_page(self):
'''翻頁'''
self.browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
time.sleep(3)
if self.browser.page_source.find('pn-next disabled') == -1:
self.browser.find_element_by_class_name('pn-next').click()
def main(self):
'''函數(shù)啟動(dòng)接口'''
self.open_browser()
self.search_keyword()
for count in range(self.page):
self.turn_page()
if __name__ == '__main__':
keyword = input("Enter the keywords to search:")
page = input("Enter the Page to download:")
spider = JdSpider(keyword=keyword, page=page)
spider.main()
網(wǎng)頁解析 及 保存:
import re
import os
import csv
import json
from lxml import etree
def format_common(_list: list):
"""格式化函數(shù)"""
_str = ''.join(_list)
_str = _str.replace('\n', '').replace('\t', '').replace('¥', '')
return _str
def format_state(_list: list):
"""格式化函數(shù)"""
_str = ' '.join(_list)
_str = _str.replace('\n', '').replace('\t', '').replace('¥', '')
return _str
class SaveData:
"""OK"""
def __init__(self, data):
self.comment_data = data[0]
self.other_data = data[1]
def judge_exists(self, path):
"""判斷文件是否已存在"""
if os.path.exists(path):
return
title = ["商鋪名稱", "說明", "價(jià)格", "評(píng)價(jià)人數(shù)", "商品名稱"]
with open(path, 'a+', encoding='utf-8', newline='') as f:
writer = csv.writer(f) # 創(chuàng)建寫 對(duì)象
writer.writerow(title) # 寫入單行
def save_to_csv(self, data: list):
"""保存為csv"""
path = r'./data/JdGoodsInfo.csv'
self.judge_exists(path)
with open(path, 'a+', encoding='utf-8', newline='') as f:
writer = csv.writer(f) # 創(chuàng)建寫 對(duì)象
writer.writerows(data) # 寫入多行
def parse_data(self):
"""解析網(wǎng)頁"""
if not self.other_data or not self.comment_data:
return
comments_item = json.loads(re.findall("jQuery\d+\((.*?)\);", self.comment_data)[0])['CommentsCount']
if len(comments_item) != 30:
return
_data = list()
xpath_html = etree.HTML(self.other_data)
xpath_items = xpath_html.xpath('//li[@class="gl-item"]')
for xpath_item, comment_item in zip(xpath_items, comments_item):
_parse = xpath_item.xpath
shop = format_common(_parse('.//div[@class="p-shop"]//text()'))
icons = format_state(_parse('.//div[@class="p-icons"]//text()'))
price = format_common(_parse('.//div[@class="p-price"]//text()'))
name = format_common(_parse('.//div[@class="p-name p-name-type-2"]//text()'))
comment = comment_item['CommentCountStr']
_data.append((shop, icons, price, comment, name))
self.save_to_csv(data=_data)
def main(self):
"""開始干活"""
self.parse_data()
mitm代碼:
# -*- coding:utf-8 -*-
# author : SunriseCai
# datetime : 2020/11/21 10:47
# software : PyCharm
import json
import mitmproxy.http
from mitmSaveData import SaveData
class Demo:
def __init__(self):
self.other_data = None
def response(self, flow: mitmproxy.http.HTTPFlow):
url = flow.request.url
if 'jd.com' not in url:
return
# 商品信息鏈接
if 'https://search.jd.com/s_new.php?keyword=' in url:
self.other_data = flow.response.text or None
# 評(píng)論鏈接
if 'https://club.jd.com/comment' in url:
comment_data = flow.response.text
SaveData([comment_data, self.other_data]).main()
comment_data, self.other_data = None, None
addons = [
Demo(),
]
遺留問題:
- 搜索的首頁不是 XHR 形式加載出來的宴凉,這個(gè)不想做適配了誊锭。
4.2 mitmproxy + Appium
手機(jī)端自動(dòng)化爬蟲
案例說明:
- Appium 自動(dòng)翻頁,
- mitmproxy 進(jìn)行信息采集弥锄,
- 在指定 App丧靡,輸入 指定關(guān)鍵詞 以及 向下滑動(dòng)的數(shù)量次數(shù),即可籽暇。