2020-01-13
抓包工具很多,mitmproxy 的气质比较特别:它既能像传统代理一样看请求响应,又能用 Python 写脚本,把“看一眼”升级成“自动处理一批”。
如果你在调试自己的 Web 服务、移动 App、接口网关、爬虫测试环境,mitmproxy 很适合放在工具箱里。它能帮你看清楚请求到底发了什么、响应到底回了什么,也能在授权环境里做 Mock、过滤、重放、记录。
本文只讨论自有系统、测试环境、授权设备里的调试用法。不要把代理工具用于未授权流量截获、凭据窃取或绕过第三方防护。
mitmproxy 的位置很简单:客户端和服务端之间。
Client -> mitmproxy -> Server
Client <- mitmproxy <- Server
客户端把 HTTP 或 HTTPS 请求交给 mitmproxy,mitmproxy 再把请求转发给目标服务。响应回来时,也会先经过 mitmproxy,再回到客户端。
这样你就能看到完整的请求头、请求体、响应状态、响应头和响应体。更好玩的是,mitmproxy 不只是“展示流量”,它还支持拦截、修改、保存、重放和脚本化处理。
mitmproxy 家族里常见三个命令:
mitmproxy
mitmweb
mitmdump
mitmproxy 是终端交互界面,适合在命令行里边看边筛。
mitmweb 带 Web 界面,适合喜欢浏览器操作的人,查看请求响应更直观。
mitmdump 偏自动化,适合跑 Python addon、保存流量、接入脚本任务。
初学建议从 mitmweb 开始,看得清楚;写脚本时切到 mitmdump,更干净。
如果你用 Python 环境,官方支持通过 PyPI 安装:
pip install mitmproxy
也可以用系统包管理器或官方镜像。具体取决于你的开发环境和团队部署方式。
启动一个最普通的代理:
mitmweb --listen-host 127.0.0.1 --listen-port 8080
或者使用终端界面:
mitmproxy --listen-host 127.0.0.1 --listen-port 8080
然后在浏览器、App、脚本或测试客户端里,把 HTTP 代理设置为:
127.0.0.1:8080
这就是 Regular 模式,也就是最直接、最稳的入口。
HTTP 是明文,mitmproxy 能直接看。
HTTPS 多了一层 TLS。客户端会验证服务端证书,如果 mitmproxy 想展示 HTTPS 内容,就需要让客户端信任 mitmproxy 生成的本地 CA 证书。
常见流程是:
启动 mitmproxy
配置客户端代理
访问 mitm.it
按客户端系统安装证书
重新发起请求
证书只应该安装在自己的测试设备、开发机或授权环境里。调试结束后,如果不再需要,可以把代理配置和证书信任清理掉。
这里也要补一句:有些 App 会做证书绑定,这属于应用自己的安全设计。本文不讨论绕过证书绑定,只讨论你有权限调试的普通代理链路。
mitmproxy 支持多种代理模式,但日常调试不需要一口气全背下来。
Regular 模式:
mitmproxy --mode regular
客户端显式配置代理,最适合浏览器、脚本、可控 App 调试。
Reverse 模式:
mitmproxy --mode reverse:http://127.0.0.1:9000 --listen-port 8080
它把 mitmproxy 放在服务前面。客户端访问 8080,mitmproxy 再转发到 9000。本地服务联调、接口 Mock、网关行为观察都挺顺手。
Upstream 模式:
mitmproxy --mode upstream:http://proxy.example:8080
它会把流量继续交给上游代理。公司内网、测试网关链路里可能用得上。
透明代理、WireGuard、Local Capture 等模式更适合特殊网络场景。能用 Regular 就先用 Regular,排查成本低很多。
接口一多,流量列表会很吵。mitmproxy 支持过滤表达式,可以只看你关心的东西。
比如只看某个域名:
~d api.example.com
只看响应状态为成功的请求:
~c 200
只看 URL 中包含某段路径的请求:
~u /api/order
只看 POST 请求:
~m POST
这些过滤表达式在交互界面和脚本里都很实用。调试接口时,先过滤再分析,效率会高很多。
如果你想把一次调试过程保存下来,可以用:
mitmdump -w flows.mitm
读取保存的流量:
mitmdump -r flows.mitm
这适合做问题复盘、接口变更对比、回归测试样本沉淀。
也可以在界面里选中某条请求进行重放。比如某个接口偶发失败,你可以改参数、改 Header,再重发看看服务端如何响应。
mitmproxy 的 addon 机制允许你用 Python 写钩子函数。请求经过时触发 request,响应回来时触发 response。
先写一个最小脚本 logger.py:
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
print("REQ", flow.request.method, flow.request.pretty_url)
def response(flow: http.HTTPFlow) -> None:
print("RESP", flow.response.status_code, flow.request.pretty_url)
运行:
mitmdump -s logger.py
只要流量经过,就会打印请求方法、URL 和响应状态码。
本地联调时,你可能希望所有请求都带上一个标记,方便服务端日志筛选。
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if flow.request.host == "api.example.com":
flow.request.headers["X-Debug-Source"] = "mitmproxy"
这类脚本只适合自己的服务或授权测试环境。不要给第三方服务乱加业务 Header,结果不可控,也不礼貌。
前端等后端接口时,mitmproxy 可以临时改响应体,做一个轻量 Mock。
import json
from mitmproxy import http
def response(flow: http.HTTPFlow) -> None:
if flow.request.path == "/api/user/profile":
data = {
"id": 1001,
"name": "debug-user",
"role": "tester",
"vip": True,
}
flow.response = http.Response.make(
200,
json.dumps(data, ensure_ascii=False),
{"Content-Type": "application/json; charset=utf-8"},
)
这个脚本的意思是:只要路径命中 /api/user/profile,就直接返回一段测试 JSON,不再使用真实响应。
这招很适合前端页面联调、异常分支验证、灰度字段验证。
如果你想把某些接口响应沉淀成样本,可以在 addon 里写文件。
import json
from pathlib import Path
from mitmproxy import http
OUTPUT_DIR = Path("captured_json")
OUTPUT_DIR.mkdir(exist_ok=True)
def response(flow: http.HTTPFlow) -> None:
content_type = flow.response.headers.get("content-type", "")
if "application/json" not in content_type:
return
if not flow.request.path.startswith("/api/"):
return
body = flow.response.get_text(strict=False)
try:
parsed = json.loads(body)
except json.JSONDecodeError:
return
safe_name = flow.request.path.strip("/").replace("/", "_") or "root"
target = OUTPUT_DIR / f"{safe_name}.json"
target.write_text(
json.dumps(parsed, ensure_ascii=False, indent=2),
encoding="utf-8",
)
这比手动复制接口响应舒服很多。调试一轮下来,样本文件自然就攒好了。
有些请求在调试时不想真的发出去,比如埋点、上传、某些副作用接口。可以直接在请求阶段返回响应。
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
blocked_paths = {
"/api/event/track",
"/api/debug/upload",
}
if flow.request.path in blocked_paths:
flow.response = http.Response.make(
204,
b"",
{"X-Blocked-By": "mitmproxy"},
)
这样客户端会收到一个空响应,真实服务不会接到这条请求。
函数式脚本很轻,但业务复杂后,用类会更清爽。
from mitmproxy import ctx, http
class ApiInspector:
def load(self, loader):
loader.add_option(
name="target_host",
typespec=str,
default="api.example.com",
help="只处理指定 host 的请求",
)
def request(self, flow: http.HTTPFlow) -> None:
if flow.request.host != ctx.options.target_host:
return
flow.request.headers["X-Trace-From"] = "mitmproxy-addon"
ctx.log.info(f"request {flow.request.method} {flow.request.path}")
def response(self, flow: http.HTTPFlow) -> None:
if flow.request.host != ctx.options.target_host:
return
flow.response.headers["X-Checked-By"] = "mitmproxy-addon"
ctx.log.info(f"response {flow.response.status_code} {flow.request.path}")
addons = [ApiInspector()]
运行时传入配置:
mitmdump -s inspector.py --set target_host=api.example.com
这就是 mitmproxy addon 的舒服之处:代理工具变成了一个可编排的 Python 运行环境。
如果你调试的是 Python 脚本,可以直接给 requests 配代理。
import requests
proxies = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080",
}
resp = requests.get(
"https://example.com/api/ping",
proxies=proxies,
timeout=10,
)
print(resp.status_code)
print(resp.text[:200])
如果 HTTPS 证书还没配好,requests 可能会报证书校验错误。开发测试里可以把 mitmproxy 的 CA 证书路径交给 verify:
resp = requests.get(
"https://example.com/api/ping",
proxies=proxies,
verify="/path/to/mitmproxy-ca-cert.pem",
)
不建议为了省事长期关闭证书校验。证书校验本来就是 HTTPS 安全链路的一部分,调试完也要把配置收回来。
客户端没配置代理。
这是最常见的。先确认浏览器、系统代理、脚本代理参数到底有没有指向 mitmproxy。
证书没安装或没信任。
HTTP 能看,HTTPS 报错,多半要检查 CA 证书信任。
代理监听地址不对。
本机调试用 127.0.0.1 没问题;手机连电脑代理时,需要监听局域网地址,比如:
mitmweb --listen-host 0.0.0.0 --listen-port 8080
同时要确认防火墙允许访问。
客户端自己绕开了系统代理。
有些 SDK、App 或运行环境不走系统代理,需要在应用内部配置代理,或者换适合的捕获模式。
响应内容是压缩的。
mitmproxy 通常能处理常见压缩,但脚本里读写响应体时,要注意 Content-Encoding 和内容类型。
mitmproxy 不只是临时抓包工具,也可以变成团队里的调试基建。
比如:
它最大的价值不是“我能看到流量”,而是“我能把流量变成可处理的数据”。
mitmproxy 的核心能力可以概括成三句话:
把客户端流量导进来
把请求响应看清楚
用 Python 把规则跑起来
入门时,用 mitmweb + Regular 模式 看清楚链路;写脚本时,用 mitmdump -s addon.py 自动处理;服务端联调时,再考虑 Reverse 模式。
只要边界守住,它就是一个非常顺手的 HTTP 调试工具:能看、能改、能存、能重放,还能用 Python 接管复杂规则。
参考资料: