跳转至

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 基础配置

安装后按顺序做四件事,顺序错了会反复踩坑:

  1. Tools → Options → Connections:Fiddler listens on port 填 8888;要抓手机/局域网设备,勾选 Allow remote computers to connect。改完必须重启 Fiddler,端口监听才会生效。
  2. Tools → Options → HTTPS:勾选 Capture HTTPS CONNECTs 与 Decrypt HTTPS traffic。下拉里 ...from all processes 表示所有进程都解密;选 ...from browsers only 时 App 流量仍是隧道。
  3. 同一页点 Actions → Trust Root Certificate,把 Fiddler 根证书装进 Windows 受信任根。弹窗选“是”。
  4. 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 常报:

remount of /system failed: Read-only file system

三条可行路径:

环境 做法
模拟器 启动参数加 -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):

  1. 设置 → 通用 → VPN 与设备管理,安装 Fiddler 证书描述文件。
  2. 设置 → 通用 → 关于本机 → 证书信任设置,把 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 重放

  1. 在会话列表选中请求,右键 Copy → Just URL 或直接拖到右侧 Composer 标签。
  2. Composer 里选 Raw,可以逐字改请求行、Header、Body。
  3. 点 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,改回默认脚本即可恢复。

十二、检查清单

  1. 8888 端口在监听,Allow remote computers to connect 已勾选并重启过。
  2. Decrypt HTTPS traffic 已开,且范围是 all processes。
  3. 根证书已装进系统信任区;Android 7+ 已按 subject_hash_old 放进系统 CA 目录。
  4. iOS 已打开“证书信任设置”里的全信任开关。
  5. 抓不到时先关 Filters,再关 HTTPS 解密对比,确认是过滤还是证书问题。
  6. App 抓不到时依次排查:用户证书、SSL Pinning、QUIC/HTTP3、代理绕过。
  7. Mock/断点规则按从上到下匹配,宽泛规则不要放第一条。
  8. 抓包文件含 Cookie/token,用完删除,不外传。

相关阅读