Charles、Fiddler 与 mitmproxy HTTPS 抓包工具手册
登录失败时,先证明请求走到了哪里
测试环境的移动端登录一直返回“网络异常”,服务端访问日志却没有对应请求。浏览器访问同一接口正常,App 重试仍失败。此时继续猜网关、DNS 或业务代码都很昂贵,最短路径是取得三段证据:客户端是否连接代理、TLS 是否完成、代理是否收到上游响应。HTTPS 抓包工具的价值不只是“看请求”,而是把一次黑盒失败拆成可验证的状态转换。
先用测试账号和无敏感内容的请求复现。抓包主机只监听回环地址;需要手机接入时才临时监听局域网地址,并用主机防火墙把来源限制到测试设备。局域网里存在其他使用者时还要启用代理认证;防火墙限制来源地址解决“谁能连”,代理认证解决“连入者是谁”,两者不能互相替代。目标域名应是自有测试服务,真实用户流量、个人账号和生产令牌不进入会话。Charles、Fiddler Everywhere 和 mitmproxy 都是前向代理:客户端先连接代理,代理再连接服务端。HTTPS 解密还会形成“客户端到代理”和“代理到服务端”两条 TLS 连接,所以“代理可达”和“客户端信任抓包 CA”是两个独立关口。
如果界面中完全没有客户端连接,先查 IP、端口、VPN、局域网隔离和客户端是否绕过系统代理。出现 CONNECT 却没有明文,说明 TCP 代理已经生效,问题收窄到 CA 信任、HTTPS 解密开关或目标域名规则。看到请求但上游列为空或为 502,再查代理主机到服务端的 DNS、证书链、mTLS 和出口网络。这个证据顺序比“换一个抓包工具试试”更可靠。
安装后先跑通一条无密钥链路
Charles 从官方下载页安装,首次运行后在 Proxy Settings 确认监听端口,在 Help > SSL Proxying 中安装 Charles Root Certificate。Charles 默认不会解密所有站点,目标 host 还要进入 SSL Proxying Settings;SSL Proxying 说明给出了这一行为。
跨平台 GUI 团队更适合从 Fiddler Everywhere 开始。Settings > Connections 控制代理监听和远程连接,Settings > HTTPS 控制 CA 信任与 HTTPS capture。系统捕获初始状态只记录 HTTP;启用 HTTPS 解密前必须信任它生成的 CA。个人开发机优先装入当前用户证书库,机器证书库会影响所有用户且需要更高权限。Fiddler Classic 保留给已有 Windows 自动化或 SAZ 工作流,不把它当跨平台新基线。
mitmproxy 适合命令行复现和自动化。macOS 可用 brew install --cask mitmproxy,Windows 使用官方安装器,Linux 优先官方独立二进制;需要额外 Python 依赖的 addon 时可用 uv tool install mitmproxy。安装后先确认三个入口来自同一发行版本:
mitmproxy --version
mitmweb --version
mitmdump --versionmitmproxy 是终端交互界面,mitmweb 提供 Web UI,mitmdump 适合非交互记录和脚本。先启动只允许本机连接的 regular proxy:
mitmweb --listen-host 127.0.0.1 --listen-port 8080另开终端执行一条 HTTP 请求:
curl --proxy http://127.0.0.1:8080 http://example.com/ -o /dev/null -w '%{http_code}\n'界面应出现 GET http://example.com/ 和响应状态。若 curl 能直连却在代理命令中报 Failed to connect,证据指向监听地址或端口,而不是证书。手机接入时把监听地址改为测试网卡 IP,并在设备 Wi-Fi 中填写该 IP 与端口;验证完成后立即恢复 127.0.0.1。
正向实验:从 CONNECT 走到 HTTPS 明文
首次运行 mitmproxy 会在 ~/.mitmproxy 生成独立 CA。mitmproxy-ca.pem 同时包含私钥和证书,不能分发;客户端只需要 mitmproxy-ca-cert.pem、.cer 或 .p12 中适合其平台的证书。客户端已经指向代理后访问 http://mitm.it,可按设备类型安装证书。仅为命令行实验时,无须改系统信任库,curl 可以只在本次调用信任 CA:
curl \
--proxy http://127.0.0.1:8080 \
--cacert "$HOME/.mitmproxy/mitmproxy-ca-cert.pem" \
https://api.example.test/health \
-H 'X-Debug-Case: capture-ok' \
-o /dev/null -w '%{http_code}\n'把 api.example.test 换成你有权调试的测试服务。预期有三份相互印证的结果:curl 输出业务状态码;mitmproxy flow 显示 CONNECT 后的 GET /health、X-Debug-Case 和响应;服务端日志出现同一个调试标识。缺少服务端记录而代理已有响应通常意味着看错环境或日志,代理没有上游响应则继续检查代理主机的 DNS 与出口。
需要保存最小证据时,先创建只有当前用户可读写的临时目录,再按域名过滤:
install -d -m 0700 captures
mitmdump \
--listen-host 127.0.0.1 \
--listen-port 8080 \
--set flow_detail=1 \
-w captures/api-example.flow \
'~d api.example.test'listen_host 决定代理暴露在哪个地址,listen_port 决定客户端填写的端口,flow_detail=1 控制控制台细节,-w 保存可重放的二进制 flow,末尾过滤表达式限制写入域名。监听地址本身不验证客户端身份;一旦离开回环地址,应同时使用主机防火墙和工具的代理认证能力,并在任务结束后撤销。过滤只减少采集结果,不构成授权;目标服务和测试账号仍须属于已批准的调试任务。
Charles 中的等价操作是把 host 加入 Proxy > SSL Proxying Settings,在 Structure 视图按 host/path 收窄后执行请求。Fiddler Everywhere 中先启用 Capture HTTPS traffic,再用过滤器或 Rules 只显示测试 host。Charles 的 Rewrite/Map Local、Fiddler Rules 和 mitmproxy addon 都能改变流量,因此先完成纯观察基线,再启用改写;否则你可能是在调试工具制造的新故障。
反向实验:故意不信任 CA
保持代理运行,去掉 --cacert 再请求同一地址:
curl \
--proxy http://127.0.0.1:8080 \
https://api.example.test/health \
-o /dev/null未把 mitmproxy CA 放入系统信任库时,预期 curl 返回证书校验错误,常见证据是 curl: (60) SSL certificate problem。代理事件日志已经有 client connect,且可能看见 CONNECT,但没有完整 HTTP 响应。重新加回 --cacert 后请求成功,便证明代理可达和上游服务都不是根因,变化点只有客户端信任链。
这个反例也解释了“浏览器能抓、Java 不能抓”:浏览器可能读取系统或自己的证书库,Java 进程读取当前运行 JDK 的 truststore。不要为了让实验变绿而关闭 TLS 校验。短时验证可以给该进程指定独立 truststore;确需导入时,要记录 JDK 路径、alias 和证书指纹,并准备对应删除命令。App 启用 certificate pinning 或服务使用 mTLS 时,拒绝抓包 CA 可能是安全控制正常工作,应切换到自有 debug 包、测试证书、服务端日志或 trace,而不是绕过保护。
用可撤销改写证明客户端分支
“客户端是否正确处理 503”不需要真的停服务。下面的完整 addon 只对测试域名和带调试头的请求生效,避免误伤同机其他流量:
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if (
flow.request.pretty_host == "api.example.test"
and flow.request.headers.get("X-Debug-Case") == "force-503"
):
flow.response = http.Response.make(
503,
b'{"code":"UPSTREAM_UNAVAILABLE"}',
{"Content-Type": "application/json", "X-Debug-Proxy": "mitmproxy"},
)将代码临时保存为工作区外的 force_503.py,启动 mitmdump -s force_503.py,然后发送带 X-Debug-Case: force-503 的请求。预期客户端收到 503,响应头含 X-Debug-Proxy: mitmproxy,服务端没有该请求;删除调试头后,服务端重新收到请求。三项证据共同证明命中的是代理短路分支。实验结束即停止进程并删除临时脚本,不把调试改写留成常驻规则。
项目接入的是流程,不是个人 CA
仓库可以记录“谁批准、抓哪些 host/path、使用哪个测试账号、如何脱敏、保存多久、怎样销毁”,也可以忽略 *.chls、*.saz、*.har、*.flow 和 captures/。不要提交任何工具 CA、私钥、真实代理地址、会话文件或带令牌的命令。问题单只保留工具与版本、客户端构建标识、代理模式、目标域名、相对时间窗口、trace ID、观察结果和清理结果。
会话文件比普通访问日志更敏感。它可能同时保存 URL 查询参数、Cookie、Authorization、请求与响应体、设备标识、内部域名和文件内容。Fiddler Everywhere 的自动 sanitization 能降低风险,但对非结构化、压缩、加密或二进制内容不能保证完全清除;导出后仍需人工复核。Fiddler 的云分享会扩大访问主体,Charles session 和 mitmproxy flow 上传工单也会扩大留存面。默认做法应是只截取必要文本证据并手工脱敏,原始会话留在加密临时目录,超过处理窗口自动销毁。
项目需要稳定复现网络错误时,优先把 addon 变成测试夹具,并让它只接受固定 host、固定标记头和无真实凭证的合成请求。CI 中的 mitmdump 进程要有启动探针、最大运行时长、磁盘上限和 finally 清理;失败语义是“夹具未启动或预期 flow 未出现”,不能把代理超时误报成业务失败。
清理回滚必须留下反证
先停止 capture 和所有 rewrite/rules,再解除客户端代理;随后从客户端信任库删除抓包 CA,删除 mitmproxy 的临时 flow、Charles session、Fiddler snapshot 和导出文件。若决定删除 ~/.mitmproxy,应先确认其中没有仍需保留的受控配置;目录内的 CA 私钥一旦删除,下次启动会生成新 CA,旧客户端信任将自然失效。
清理后执行两条验证:直连测试服务应成功,显式连接已关闭的代理端口应失败。证书库中按工具名称搜索不到 CA,进程列表和监听端口中也不应再有抓包工具。只有“关掉窗口”而没有撤销代理和 CA,下一次网络异常仍可能来自残留配置。
机制、容量与选型判断
客户端发出 CONNECT host:443 后,代理先与上游握手,读取服务端证书的主机名与 SAN,再即时生成一张由本地抓包 CA 签名的站点证书交给客户端。客户端接受它后,代理才能获得 HTTP 明文,执行过滤、断点、重写或保存,再通过第二条 TLS 连接发往上游。每个 flow 会持有请求头、请求体、响应头、响应体和时序信息;大文件、流式响应、WebSocket 和长连接会同时推高内存、磁盘与文件句柄。
容量控制不能只设“抓十分钟”。先按 host、path、method 和调试标识收窄,再禁用无关响应体保存;监控 flow 数、会话文件字节数、进程 RSS、失败连接数和磁盘剩余量。阈值由测试流量基线和终端容量决定,触发后应停止新采集并保留最小故障证据,而不是写满开发机。代理还可能改变 HTTP/2、HTTP/3、连接复用、压缩、流式传输和超时,所以关键结论必须与不走代理的请求、服务端日志和 trace 交叉验证。
选型时,Charles 的优势是成熟的桌面交互、Map Local/Remote、Rewrite 与移动端引导,代价是商业授权和 GUI 流程不易审计;Fiddler Everywhere 适合跨平台 GUI、规则和团队协作,但账号、授权、云分享和本地 MCP 等功能都会扩大身份与数据治理面;Fiddler Classic 更适合 Windows 遗留资产。mitmproxy 开源、脚本化和可复现性强,代价是团队要维护 addon、Python 依赖、进程生命周期与证据清理。
架构决策的第一问不是哪个界面顺手,而是谁需要观察、是否需要稳定重放、会话能否离开终端、许可证和云能力是否合规。个人临时 GUI 排障可选 Charles 或 Fiddler Everywhere;需要代码审查、CI 夹具和批量脱敏时优先 mitmproxy;涉及生产敏感数据、第三方应用 pinning 或未获授权设备时,三者都不应成为默认路径,应改用服务端可观测性和受控测试环境。
团队长期治理要给抓包 CA 设置所有者和退出动作,给会话设置数据分级、访问控制、留存期限与销毁证据,给脚本设置代码审查和依赖升级节奏。每次工具升级后用同一组 HTTP、HTTPS、失败证书、重写和大响应样例做回归;当 flow 数与磁盘占用回到稳定基线、证书和监听端口均已消失,才算一次抓包任务真正结束。
