Fiddler 抓包与 HTTPS 解密¶
一、先看现象:为什么全是 CONNECT¶
打开 Fiddler 后会话列表只有一行行 CONNECT api.example.com:443,响应是 200 Connection Established,Inspectors 里 body 是乱码。这不是“抓不到包”,而是只看到了隧道,没有解密。
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
User-Agent: Mozilla/5.0 ...
HTTP/1.1 200 Connection Established
FiddlerGateway: Direct
StartTime: 11:20:31.402
Connection: close
CONNECT 是客户端让代理帮忙建 TCP 隧道。Fiddler 只有在开启 HTTPS 解密后,才会在隧道里做一次中间人 TLS 握手,从而看到明文请求。
二、Fiddler Classic 基础配置¶
安装后按顺序做四件事,顺序错了会反复踩坑:
Tools → Options → Connections:Fiddler listens on port填8888;要抓手机/局域网设备,勾选Allow remote computers to connect。改完必须重启 Fiddler,端口监听才会生效。Tools → Options → HTTPS:勾选Capture HTTPS CONNECTs与Decrypt HTTPS traffic。下拉里...from all processes表示所有进程都解密;选...from browsers only时 App 流量仍是隧道。- 同一页点
Actions → Trust Root Certificate,把 Fiddler 根证书装进 Windows 受信任根。弹窗选“是”。 Actions → Export Root Certificate to Desktop,得到FiddlerRoot.cer,给手机/其他机器用。
验证是否生效:
# 1. 端口在监听
netstat -ano | findstr :8888
# TCP 0.0.0.0:8888 0.0.0.0:0 LISTENING 23144
# 2. 本机走代理请求,看 Fiddler 里是否出现明文(而不是 CONNECT)
curl.exe -sS -x http://127.0.0.1:8888 https://api.example.com/health -o NUL -w "%{http_code}`n"
如果 curl 返回 curl: (60) SSL certificate problem: unable to get local issuer certificate,说明根证书没被信任;此时用 --cacert FiddlerRoot.pem 只能临时绕过,不能证明 App 侧也信任。
三、Android 7+ 系统证书安装¶
Android 7(API 24)起,App 默认只信任系统 CA,不信任用户安装的证书。只在“设置”里装 CA,浏览器能抓、App 仍报:
javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
要让 App 信任,必须把证书放进系统 CA 目录,文件名是 <subject_hash_old>.0:
# 1. DER 转 PEM
openssl x509 -inform DER -in FiddlerRoot.cer -out FiddlerRoot.pem
# 2. 计算旧版 subject hash(Android 用的就是这个)
HASH=$(openssl x509 -inform PEM -in FiddlerRoot.pem -subject_hash_old | head -n 1)
echo "$HASH"
# e5b8a3f1
# 3. 按 hash 命名
cp FiddlerRoot.pem "${HASH}.0"
# 4. 推进系统证书目录(需要 root 或可写 system 的模拟器)
adb root
adb remount
adb push "${HASH}.0" /system/etc/security/cacerts/
adb shell chmod 644 "/system/etc/security/cacerts/${HASH}.0"
adb reboot
# 5. 重启后确认
adb shell ls -l /system/etc/security/cacerts/ | grep "$HASH"
Android 10 起系统分区默认只读,adb remount 常报:
三条可行路径:
| 环境 | 做法 |
|---|---|
| 模拟器 | 启动参数加 -writable-system,再 adb root && adb remount |
| 已 root 真机 | 用 Magisk 模块把证书挂到 /system/etc/security/cacerts(如 MagiskTrustUserCerts),重启生效 |
| 不可 root | 让开发出 debug 包,加 network security config(仅 debug) |
debug 包的配置(res/xml/network_security_config.xml):
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</debug-overrides>
</network-security-config>
debug-overrides 只在 android:debuggable="true" 时生效,release 包不受影响。这也是验证“是否只是证书问题”的最快手段。
iOS 的两步信任¶
iOS 装完描述文件后还要手动开全信任,否则同样报证书错误(NSURLErrorServerCertificateUntrusted,-1202):
- 设置 → 通用 → VPN 与设备管理,安装 Fiddler 证书描述文件。
- 设置 → 通用 → 关于本机 → 证书信任设置,把 Fiddler 根证书开关打开。
第二步不做,Safari 能上网但 App 的 HTTPS 请求会失败。
四、只抓指定进程¶
4.1 Filters(按 URL/域名)¶
Filters 面板勾选 Use Filters:
Hosts → Show only if URL contains:填api.example.com,只显示目标域名。Hosts → Hide if URL contains:填.css, .js, .png, .woff2,过滤静态资源。Request Headers → Flag requests with header:标记带特定 Header 的请求,便于在列表里找。Response Status Code:只看4xx/5xx,排错时非常省时间。
注意:Filters 只影响显示,不影响 Fiddler 转发;抓不到时先取消勾选 Use Filters 确认不是被过滤掉了。
4.2 按进程(FiddlerScript)¶
Filters 面板没有“按进程”选项,用脚本隐藏其他进程的会话:
class Handlers {
static function OnBeforeRequest(oSession: Session) {
// X-ProcessInfo 形如 "chrome:12345";取不到时退回 processID
var info = oSession["X-ProcessInfo"];
var pid = oSession.processID;
if (info == null || info.indexOf("chrome") < 0) {
oSession["ui-hide"] = "yes";
}
if (pid == 0) {
// pid 为 0 通常是系统/代理自身流量,按需隐藏
oSession["ui-hide"] = "yes";
}
}
}
进程名与 PID 也可以在会话列表的 Process 列直接看(右键列头可添加)。先抓一次,确认目标进程名,再写死到脚本里。
五、Composer 重放¶
- 在会话列表选中请求,右键
Copy → Just URL或直接拖到右侧Composer标签。 Composer里选Raw,可以逐字改请求行、Header、Body。- 点
Execute,新请求出现在列表里,与原请求对比响应。
重放最常遇到的三个结果:
| 结果 | 原因 | 处理 |
|---|---|---|
| 302 到登录页 | Cookie 过期 | 重新登录后复制新 Cookie |
401 + SIGN_EXPIRED |
时间戳/签名过期 | 用脚本重新签名,不能直接重放 |
409 DUPLICATE_REQUEST |
幂等键已消费 | 换 Idempotency-Key 或确认这是预期行为 |
Composer 里改 body 后建议同时检查 Content-Length;用 Raw 模式手工改长度容易发出截断请求,服务端表现是挂起或 400。
六、AutoResponder 造 Mock¶
勾选 Enable rules 与 Unmatched requests passthrough,添加规则:
METHOD:POST EXACT:https://api.example.com/api/v1/pay
REGEX:https://api\.example\.com/api/v1/orders/\d+ ./mock/order_paid.json
EXACT:https://api.example.com/api/v1/config *delay:3000
右侧 Response Action 下拉可选:
Find a file...:返回本地 JSON/HTML 文件。200_No_Content、404_NotFound、502_Proxy_Error:内置状态码响应。*delay:3000:延迟 3 秒返回,用来复现前端 loading 与超时。*redir:https://...:重定向。
造“接口 500”时,除了用内置 502,还可以放一个本地文件,内容以状态行开头:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{"code":"INTERNAL_ERROR","message":"mock failure"}
AutoResponder 的坑:规则按从上到下第一条命中生效,宽泛的 REGEX:.* 放最上面会吃掉所有规则;改完规则要按 Save,否则重启丢失。
七、断点改包¶
两种方式:
- 菜单:
Rules → Automatic Breakpoints → Before Requests(改请求)或After Responses(改响应)。 - 命令行(QuickExec,焦点在会话列表时输入):
bpu断所有请求、bpv断所有响应、bps 500只在状态码 500 时断、bpafter /api/v1/pay在指定 URL 响应后断。
命中后会话图标显示红色箭头,Inspectors → TextView 直接改,改完点 Run to Completion 放行。常用场景:
| 场景 | 改什么 | 验证点 |
|---|---|---|
| 越权 | 请求里的 userId、orderId |
是否返回他人数据 |
| 金额篡改 | body 里 price、amount |
服务端是否以自己计算为准 |
| 强制失败 | 响应状态行改成 500,body 改成错误 JSON |
前端是否有兜底提示 |
| 脏数据 | 响应里 data 改成 null 或字段类型改成字符串 |
前端是否白屏/崩溃 |
| 慢接口 | 响应前加延迟(见弱网) | loading、超时、重试 |
八、弱网模拟¶
Rules → Performance → Simulate Modem Speeds 打开后,所有请求按脚本里的延迟逐块放行。自定义在 FiddlerScript 的 OnBeforeRequest:
class Handlers {
static function OnBeforeRequest(oSession: Session) {
if (m_SimulateModem) {
// 单位:毫秒/KB。300 约等于 3.3 KB/s,接近 2G
oSession["request-trickle-delay"] = "300";
oSession["response-trickle-delay"] = "150";
}
}
}
延迟是“每 KB 加多少毫秒”,不是总延迟。想稳定复现“接口 3 秒才返回”,用 AutoResponder 的 *delay:3000 更可控;想复现“大响应体下载到一半超时”,用 trickle 更真实。
九、真实问题与处理¶
| 现象 | 根因 | 处理 |
|---|---|---|
| 全是 CONNECT | 未开 HTTPS 解密,或域名在 Skip decryption 列表 |
勾选 Decrypt HTTPS traffic;检查 Tools → Options → HTTPS → Skip decryption for hosts |
浏览器 ERR_TUNNEL_CONNECTION_FAILED |
Fiddler 未运行/端口被占/上游代理错误 | 确认 8888 监听;检查 Tools → Options → Connections → Use system proxy |
| 手机浏览器能抓、App 不能 | Android 7+ 不信任用户证书,或 App 有 SSL Pinning | 装系统证书;Pinning 需 debug 包或 Frida 绕过(仅授权测试) |
App 报 Certificate pinning failure! |
OkHttp/TrustKit 校验了证书链哈希 | 用开发提供的测试包,或与开发确认 pinning 白名单 |
| iOS App 请求失败 | 未开启“证书信任设置” | 按第三节第二步打开全信任 |
| 响应乱码 | gzip/br 压缩未解码 | Inspectors 右上角点 Decode,或 Response Body → 解压;Fiddler 默认不自动解压 |
| HTTP/2 接口行为不一致 | Fiddler Classic 解密后按 HTTP/1.1 转发,无法观察多路复用、RST_STREAM | 用 Charles 4.x / Burp 2023+,或改用 Wireshark 看 h2 帧 |
只看到 Tunnel to |
目标用了 WebSocket 或非 HTTP 协议 | WebSocket 在 Inspectors → WebSockets 看帧;其他协议用 Wireshark |
| 请求被 QUIC 绕过 | HTTP/3 走 UDP 443,不走 HTTP 代理 | 关闭 App 的 QUIC/HTTP3,或直接用 Wireshark 抓 UDP |
关于 HTTP/2:Fiddler Classic 5.x 不支持 h2 的服务端协商,它终结客户端的 TLS 后一律以 HTTP/1.1 访问上游(客户端侧也按 HTTP/1.1 呈现)。因此“只在 h2 下出现的 421、流控、GOAWAY”用 Fiddler 复现不出来,这类问题必须换工具或抓网络层。
十、与 Charles / Burp 对比¶
| 能力 | Fiddler Classic | Charles | Burp Suite |
|---|---|---|---|
| 平台 | Windows(.NET) | 跨平台(Java) | 跨平台(Java) |
| HTTP/2 | 不支持(降级 1.1) | 4.x 支持 | 2021+ 支持 |
| HTTPS 解密 | 支持,需装根证书 | 支持,需装根证书 | 支持,需装根证书 |
| 改包/Mock | AutoResponder + FiddlerScript | Map Local / Rewrite / Breakpoint | Repeater + 扩展 |
| 脚本能力 | FiddlerScript(JScript.NET) | 较弱 | BApp 生态强 |
| 安全扫描 | 无 | 无 | 有(Scanner/Intruder) |
| 典型用途 | Windows 客户端调试、快速改包 | 移动端抓包、弱网 | 渗透测试、重放与扫描 |
选择建议:只是看请求改请求,Fiddler 够用且免费;移动端 + HTTP/2 优先 Charles;要做越权/注入批量验证直接用 Burp。
十一、FiddlerScript 片段¶
去掉条件请求,强制拿 200 全量响应(排查缓存问题时用):
class Handlers {
static function OnBeforeRequest(oSession: Session) {
if (oSession.HostnameIs("static.example.com")) {
oSession.oRequest.headers.Remove("If-None-Match");
oSession.oRequest.headers.Remove("If-Modified-Since");
}
}
}
给所有响应加 CORS 头(本地联调前端跨域):
class Handlers {
static function OnBeforeResponse(oSession: Session) {
if (oSession.HostnameIs("api.example.com")) {
oSession.oResponse.headers["Access-Control-Allow-Origin"] = "*";
oSession.oResponse.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization";
}
}
}
改响应体里的状态字段(先解压再替换,避免 gzip 下替换失败):
class Handlers {
static function OnBeforeResponse(oSession: Session) {
if (oSession.uriContains("/api/v1/orders/9001")) {
oSession.utilDecodeResponse();
oSession.utilReplaceInResponse('"status":"PENDING"', '"status":"PAID"');
}
}
}
把请求转发到测试环境(Host 重写):
class Handlers {
static function OnBeforeRequest(oSession: Session) {
if (oSession.HostnameIs("api.example.com")) {
oSession.hostname = "10.20.30.40:8080";
oSession.oRequest.headers["Host"] = "api.example.com";
}
}
}
脚本改完点 Save Script 立即生效,不需要重启。写错会导致所有请求 502,改回默认脚本即可恢复。
十二、检查清单¶
- 8888 端口在监听,
Allow remote computers to connect已勾选并重启过。 Decrypt HTTPS traffic已开,且范围是 all processes。- 根证书已装进系统信任区;Android 7+ 已按
subject_hash_old放进系统 CA 目录。 - iOS 已打开“证书信任设置”里的全信任开关。
- 抓不到时先关 Filters,再关 HTTPS 解密对比,确认是过滤还是证书问题。
- App 抓不到时依次排查:用户证书、SSL Pinning、QUIC/HTTP3、代理绕过。
- Mock/断点规则按从上到下匹配,宽泛规则不要放第一条。
- 抓包文件含 Cookie/token,用完删除,不外传。