mitmproxy、mitmweb 与 mitmdump 自动化抓包手册
三个入口共享核心,但承担不同工作
Linux 使用 官方安装页提供的独立二进制。以 12.2.3 为基线,从官方 downloads 选择与架构对应的压缩包,在新目录解压,将 mitmproxy、mitmweb、mitmdump 用 install -m 0755 复制到已创建的 $HOME/.local/bin,并把该目录加入 PATH。无需 sudo 运行代理。以下实验在 Linux 普通用户的两个终端进行,不修改个人长期 ~/.mitmproxy 或宿主信任库。终端 A 先创建本次独占目录,并打印供终端 B 使用的绝对路径:
umask 077
mkdir -p "$HOME/.local/share"
MITM_LAB="$(mktemp -d "$HOME/.local/share/mitm-lab.XXXXXX")"
mkdir -m 700 "$MITM_LAB/conf" "$MITM_LAB/captures"
printf 'MITM_LAB=%s\n' "$MITM_LAB"终端 B 用刚打印的真实路径执行 umask 077; export MITM_LAB=/实际/绝对路径,再运行 test -O "$MITM_LAB" && test -d "$MITM_LAB/conf" && cd "$MITM_LAB"。不要把示例占位路径直接复制;两终端的 $PWD、shell 变量和 umask 不会自动共享。后续各个代理进程都指定相同的 $MITM_LAB/conf,且同一时间只允许一个进程占用回环 8080。
mitmproxy 发行里有三个常用入口。mitmproxy 是终端交互界面,适合在键盘驱动的 flow 列表中观察与编辑;mitmweb 使用同一代理核心并提供浏览器界面;mitmdump 面向非交互日志、保存和 addon 自动化。它们读取同一套 options 和 ~/.mitmproxy/config.yaml,却有不同的展示与生命周期,脚本或 CI 应明确使用 mitmdump,不能把桌面交互会话假装成稳定任务。
macOS 官方推荐 Homebrew cask,Windows 使用官方安装器,Linux 优先官方独立二进制。需要额外 Python 依赖的 addon 时,再考虑 uv tool install mitmproxy,避免把系统 Python、发行包与独立二进制混在同一 PATH。安装后先证明三个命令来自同一发行:
mitmproxy --version
mitmweb --version
mitmdump --versionmitmproxy --version 的版本、Python 与 OpenSSL 信息应进入排障记录。官方预编译包冻结了随发行交付的 Python、OpenSSL 和其他依赖,也不会主动联网检查更新;团队要自己建立升级节奏和回归样例。下游 Linux 发行包可能落后,遇到行为差异时先比较真实二进制来源,不用“系统已经是最新”替代版本证据。
终端 A 先以回环地址启动 regular proxy。mitmweb 的 Web 界面默认在 127.0.0.1:8081,代理监听在显式指定的 127.0.0.1:8080,两者不是同一个入口:
mitmweb --set confdir="$MITM_LAB/conf" --listen-host 127.0.0.1 --listen-port 8080终端 B 显式请求 HTTP;这里的 example.com 只用于不带身份信息的首次连通性检查,需要访问获准的公网地址:
curl -q --noproxy '' --fail-with-body --proxy http://127.0.0.1:8080 \
--connect-timeout 3 --max-time 8 http://example.com/ \
-o /dev/null -w '%{http_code}\n'curl 状态码与 flow 同时出现,证明客户端到代理和代理到上游的基础链路成立。连接拒绝指向进程、监听地址或端口;flow 有请求却没有响应才继续检查代理主机 DNS、出口和上游。regular proxy 默认端口是 8080,但显式指定能避免配置文件、旧进程和多实例造成误判。
CA 目录同时是配置状态和高价值秘密
默认运行会在 ~/.mitmproxy 生成唯一 CA;本次显式设置 confdir,所以新 CA 位于 $MITM_LAB/conf。官方证书文件说明列出了含私钥的 mitmproxy-ca.pem,以及只含公开证书的 mitmproxy-ca-cert.pem、mitmproxy-ca-cert.cer 和 mitmproxy-ca-cert.p12。不要仅凭 .p12 扩展名判断文件是否可分发;含私钥的 CA 文件不能交给客户端,也不能进入仓库、工单或共享备份。访问 mitm.it 可以按设备下载公开 CA 证书,但前提是设备已经正确连接到当前代理。
一次命令行实验可以只为当前 curl 信任 CA:
test -s "$MITM_LAB/conf/mitmproxy-ca-cert.pem" &&
curl -q --noproxy '' --fail-with-body --proxy http://127.0.0.1:8080 \
--cacert "$MITM_LAB/conf/mitmproxy-ca-cert.pem" \
--connect-timeout 3 --max-time 8 \
https://example.com/ \
-o /dev/null -w '%{http_code}\n'预期得到 200,flow 中出现解密后的 example.com 请求;如换成自己控制的测试服务,可增加唯一调试头并与服务端日志对照,同时确保代理上游也信任该服务的证书。当前实验没有将新 CA 安装进系统信任库。终端 B 再做未信任对照:
tls_rc=0
env -u CURL_CA_BUNDLE -u SSL_CERT_FILE -u SSL_CERT_DIR \
curl -q --noproxy '' --proxy http://127.0.0.1:8080 \
--connect-timeout 3 --max-time 8 --output /dev/null \
https://example.com/ || tls_rc=$?
printf 'tls_rc=%s\n' "$tls_rc"
test "$tls_rc" -eq 60预期 tls_rc=60 表示客户端不信任代理签出的证书;若此前已另行安装该 CA,此反例须改在未信任的独立客户端执行,不能为制造失败删除团队在用的根。这一对照把“代理可达”和“客户端信任代理 CA”分开。若应用启用了 certificate pinning,应优先使用自有 debug build、服务端日志或 trace;官方提供的 ignore_hosts 可让不需要观察的 pinned 域名只转发而不拦截。
mitmproxy 默认会先连接上游,读取真实证书的 CN、组织和 SAN,再动态生成拦截证书。上游证书验证仍然重要。ssl_insecure=true 会跳过它,使代理自身容易接受错误身份;不要为了让 flow 变绿而全局启用。内网测试 CA 应通过 ssl_verify_upstream_trusted_ca 提供明确 PEM,而不是关闭验证。
confdir 决定配置、CA 和其他状态的位置。为一次隔离测试设置独立目录,可以避免污染个人长期 CA;目录删除前要先撤销客户端信任。自定义 confdir 若没有 CA 会自动生成一套新的,旧客户端不会自动信任它,这也是多台开发机“同名 mitmproxy 却证书不同”的正常原因。
config.yaml 与 --set 是同一 options 系统
三个工具共享 typed options。--options 输出当前二进制真正支持的完整选项和默认值,比复制网上旧配置更可靠;命令行 flags 多数只是底层 option 的别名,交互界面修改的也是运行时 options store。
mitmdump --set confdir="$MITM_LAB/conf" --options > "$MITM_LAB/options.txt"这条命令只导出选项说明,不启动第二个监听进程。flow_detail、body_size_limit 等运行参数放到下一节真正启动 mitmdump 的命令里;当 mitmweb 仍占用 8080 时,不要另起一个默认端口的代理。
稳定团队配置可以进入 ~/.mitmproxy/config.yaml,但口令、私钥路径和内部地址不应随模板提交。cert_passphrase 放在命令行会出现在进程列表,官方也建议在配置中提供;团队更稳妥的做法是由临时权限受控文件或秘密注入层生成本次配置,并在进程退出后销毁。
mode 不只支持 regular HTTP proxy,还包括 local、transparent、SOCKS5、reverse、upstream 和 WireGuard。每种模式改变客户端接入或上游路由,不应在不了解网络责任时切换。开发侧抓浏览器或手机通常从 regular 开始;代理链环境可能用 upstream,但要分别处理客户端到 mitmproxy 的 proxyauth 与 mitmproxy 到上游的 upstream_auth。两种认证的主体和凭证不相同。
离开回环地址时,block_global 默认阻止公共 IP 来源,但它不是完整认证策略。局域网接入还应限制监听网卡、主机防火墙和设备来源,并配置 proxyauth。proxyauth 支持固定用户口令、htpasswd 或 LDAP 等形式;真实凭证不进入 shell history。任务结束恢复回环监听并撤销防火墙规则。
flow filter 决定处理对象,不决定调试授权
mitmproxy 的 flow filter 可按域名、URL、method、状态等条件筛选。末尾过滤表达式会限制交互视图或处理目标,save_stream_filter 决定哪些 flow 写入文件。它能减少数据量,却不能把未授权流量变成可抓取对象。
先在终端 A 用 Ctrl+C 停止 mitmweb,确认进程已退出、8080 不再监听,再启动下面的保存实例。它继续使用本次独占 CA,只保存随后向 example.com 发出的测试请求;flow_detail=2 控制终端输出,body_size_limit=2m 是本实验的 body 上限:
mitmdump --set confdir="$MITM_LAB/conf" \
--listen-host 127.0.0.1 \
--listen-port 8080 \
--set flow_detail=2 --set body_size_limit=2m \
--set save_stream_file="$MITM_LAB/captures/example.flow" \
--set 'save_stream_filter=~d example.com & ~u /' \
'~d example.com'终端 B 在该进程运行期间发出一条与过滤器匹配的请求:
curl -q --noproxy '' --fail-with-body --proxy http://127.0.0.1:8080 \
--connect-timeout 3 --max-time 8 --output /dev/null \
--write-out 'status=%{http_code}\n' http://example.com/预期终端 B 得到 status=200,终端 A 能看到同一 host 的 flow。随后在终端 A 按 Ctrl+C 停止保存进程,终端 B 检查 test -s "$MITM_LAB/captures/example.flow";文件不存在或为空时先检查代理进程、请求 host、filter 和终端错误,不得直接进入 replay。实验使用外部公共页面仅为连通性与保存演示;在出网受限环境可换成获准的测试服务,但请求目标、保存过滤器和后面的 replay 必须同步替换。
保存文件是 mitmproxy flow,不是普通日志。它可能包含完整 headers、cookies、body、内部域名和二进制内容。过滤只选 host 仍可能收进该 host 下的登录、上传或个人数据;应再用固定 path、method 与调试标识缩小,并使用合成账号。HAR 的 hardump 默认在退出时保存所有 flow;若配置了 save_stream_filter,还要核对实际选择范围。mitmdump 为导出保留 flow 时,长时间任务可能同时扩大内存和磁盘占用。
大 body 可以用 body_size_limit 拒绝超过上限的内容,或用 stream_large_bodies 直接流向客户端。流式 body 默认不会被存储,也无法再修改;打开 store_streamed_bodies 会重新增加内存消耗。决定这些参数时先回答是要观察元数据、保存内容还是改写内容,不能三项都默认开启。
addon 把一次改写变成可审查代码
addon 通过事件 hook 接收 HTTPFlow 等对象,可以观察、阻断、改写和生成响应。下面的脚本只对测试 host、固定 path 和调试头生效,用来验证客户端对 503 的处理:
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if (
flow.request.pretty_host == "api.example.test"
and flow.request.path == "/health"
and flow.request.headers.get("X-Debug-Case") == "mitmproxy-503"
):
flow.response = http.Response.make(
503,
b'{"code":"UPSTREAM_UNAVAILABLE"}',
{"Content-Type": "application/json", "X-Debug-Proxy": "mitmproxy"},
)终端 B 先把上面的 Python 代码保存为 $MITM_LAB/force_503.py。终端 A 确认上一节保存实例已退出,且文件非空后启动:
test -s "$MITM_LAB/force_503.py" &&
mitmdump --set confdir="$MITM_LAB/conf" --set connection_strategy=lazy \
--listen-host 127.0.0.1 --listen-port 8080 -s "$MITM_LAB/force_503.py"connection_strategy=lazy 使这个 HTTP 短路实验无需先连接不存在的测试域名。终端 B 执行:
curl_rc=0
status=$(curl -q --noproxy '' --silent --show-error \
--proxy http://127.0.0.1:8080 --connect-timeout 3 --max-time 8 \
-D "$MITM_LAB/response.headers" -o "$MITM_LAB/response.json" \
-w '%{http_code}' -H 'X-Debug-Case: mitmproxy-503' \
http://api.example.test/health) || curl_rc=$?
printf 'curl_rc=%s status=%s\n' "$curl_rc" "$status"
test "$curl_rc" -eq 0 && test "$status" = 503 &&
grep -i '^X-Debug-Proxy: mitmproxy' "$MITM_LAB/response.headers" &&
grep -Fx '{"code":"UPSTREAM_UNAVAILABLE"}' "$MITM_LAB/response.json"预期响应体为 {"code":"UPSTREAM_UNAVAILABLE"}。移除调试头后该虚构域名应出现解析/上游错误,不再得到该固定 503;连接真实获准测试 upstream 时,还应在服务端日志确认无头请求正常到达。负例不使用 --fail-with-body,先检查传输退出码再断言精确 503,避免把预期错误当成测试失败。
进入仓库的 addon 应有依赖锁、静态检查和正反测试,配置用显式 option 注入。第三方 addon 与其 Python 依赖拥有读取和修改全部 flow 的能力,安装来源与代码审查应按高权限调试工具处理。多文件夹具要给出启动探针、最大运行时间、收到预期 flow 的成功条件和 finally 清理。
replay 先冻结匹配语义
client replay 把保存的请求再次发往上游,server replay 用保存响应回答匹配的新请求。两者不是浏览器级行为回放:认证、时钟、随机 ID、Cookie、环境和副作用不会自动合理化。重放写请求前应替换身份、确认幂等性并准备数据清理。
server replay 默认按请求寻找 flow,并会消耗匹配项;server_replay_reuse 可以重复使用同一响应。忽略 host、port、body 或参数会扩大匹配,可能把不相关请求答成旧响应。团队夹具应明确哪些 header 和参数参与身份,未匹配请求是 forward、kill 还是返回固定错误。默认 forward 会把漏匹配请求送到真实上游,离线测试若要求绝不出网,应改成明确阻断并用网络反证验证。
connection_strategy=lazy 可以推迟上游连接,便于离线 server replay;eager 更适合协议探测和镜像 TLS ALPN。策略改变连接时机,也会改变日志和失败位置。回放结果必须写出实际 strategy、匹配规则与额外请求处理方式,否则“本地重放通过”无法重现。
前面保存的是对 http://example.com/ 的测试 flow。先在终端 A 用 Ctrl+C 停止 addon 实例,终端 B 再确认文件非空,然后终端 A 启动只回放且不转发未匹配请求的实例:
test -s "$MITM_LAB/captures/example.flow" &&
mitmdump --set confdir="$MITM_LAB/conf" \
--listen-host 127.0.0.1 --listen-port 8080 \
--server-replay "$MITM_LAB/captures/example.flow" --set connection_strategy=lazy \
--set server_replay_reuse=true --set server_replay_extra=kill终端 B 发送正反两条请求:
curl -q --noproxy '' --fail-with-body --proxy http://127.0.0.1:8080 \
--connect-timeout 3 --max-time 8 --output /dev/null \
--write-out 'replay_status=%{http_code}\n' http://example.com/
miss_rc=0
curl -q --noproxy '' --silent --show-error --proxy http://127.0.0.1:8080 \
--connect-timeout 3 --max-time 8 --output /dev/null \
http://example.com/not-in-flow || miss_rc=$?
printf 'miss_rc=%s\n' "$miss_rc"
test "$miss_rc" -ne 0第一条应取得保存的响应及 replay_status=200;第二条预期客户端传输错误,且终端 A 记录 replay miss,没有上游请求。server_replay_extra=kill 不生成 HTTP 404,不能用状态码 404 作为未命中判断。参数、过滤器与回放行为见 mitmproxy options。测试结束在终端 A 按 Ctrl+C,确认 8080 不再监听后再处理 flow 和临时 CA,不复用含真实写入请求的捕获。
CI 里的 mitmdump 必须像临时服务一样被回收
CI 中先创建权限受限的临时 confdir 与输出目录,启动 mitmdump 后轮询监听端口或专用健康请求,探针通过才运行测试。进程要有限时、磁盘上限和可区分的退出码;测试结束无论成功失败都发送终止信号、等待进程退出、清除代理环境变量并删除 CA 私钥和 flow。
失败语义至少区分代理未启动、客户端未连接、TLS 未解密、addon 未命中、上游失败和业务断言失败。只让测试等待到超时,会把所有层次压成一个红灯。日志中禁止打印 Authorization、Cookie、完整 body 和代理认证口令;保存 flow 之前先执行过滤和脱敏,制品上传再做二次审查。
升级 mitmproxy 后使用同一组 HTTP、受信 HTTPS、不受信 CA、addon 短路、大 body、WebSocket 和退出清理样例回归。二进制内含冻结依赖且不会主动检查更新,长期钉死旧版与每次都追最新同样不可取;按安全修复、协议兼容和夹具稳定性决定升级窗口。
删除 CA 之前先证明没有客户端继续依赖
先停止代理任务,再解除客户端代理。本实验仅用 curl --cacert 让单次请求信任隔离 CA,没有修改宿主信任库;如另行导入系统、浏览器、设备或 JDK,应先在对应信任库核对指纹并移除本次 CA,不能把删除本地文件当成信任撤销。
终端 B 先确认刚才打印的 MITM_LAB 指向本次独占目录,列出待清理文件;终端 A 确认代理进程已退出。下面仅删除本实验已知的文件,任何额外文件都由 rmdir 拒绝静默清理:
if test -n "${MITM_LAB:-}" && test -d "$MITM_LAB/conf" &&
[[ "$MITM_LAB" == "$HOME"/.local/share/mitm-lab.?????? ]]; then
find "$MITM_LAB" -maxdepth 2 -type f -print
rm -i -- "$MITM_LAB/captures/example.flow" "$MITM_LAB/force_503.py" \
"$MITM_LAB/response.headers" "$MITM_LAB/response.json" "$MITM_LAB/options.txt"
rm -i -- "$MITM_LAB/conf/mitmproxy-ca.pem" \
"$MITM_LAB/conf/mitmproxy-ca-cert.pem" \
"$MITM_LAB/conf/mitmproxy-ca-cert.cer" \
"$MITM_LAB/conf/mitmproxy-ca-cert.p12"
rmdir -- "$MITM_LAB/captures" "$MITM_LAB/conf" "$MITM_LAB"
else
printf '拒绝清理:先核对本次打印的 MITM_LAB 绝对路径\n' >&2
fi文件缺失时 rm -i 会报告,按清单核对即可;若仍有其他文件,rmdir 会失败,应先确认来源与是否需要留存,不能改用递归强删。直连测试服务应成功,显式连接关闭后的代理端口应失败;若本次 CA 曾被导入信任库,还要确认相应证书已移除。下一次独立实验生成新 CA 后,旧客户端不应自动信任新根。
需要键盘交互和自动化脚本时,mitmproxy 的优势很明显;代价是团队承担 Python addon、配置、进程、流文件与 CA 生命周期。桌面 Map/Rewrite 和移动端引导更适合 Charles,跨平台 GUI 与 Rules/Composer 协作更适合 Fiddler。无论选哪一个,代理捕获都只服务获准测试流量,不能代替服务端可观测性和正式安全审计平台。
