Skip to content

中间人攻击 (MITM) 原理与防护

🎯 理解 HTTPS 被抓包的原理,以及如何防护敏感数据


1. 什么是中间人攻击

1.1 基本概念

┌─────────────────────────────────────────────────────────────────────────┐
│                     中间人攻击 (Man-in-the-Middle)                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  定义:                                                                  │
│  攻击者秘密地拦截并可能修改双方之间的通信,双方都认为自己在直接通信       │
│                                                                         │
│  正常通信:                                                              │
│  ┌────────┐                                    ┌────────┐              │
│  │ Client │ ◀══════════ 加密通信 ══════════▶ │ Server │              │
│  └────────┘                                    └────────┘              │
│                                                                         │
│  中间人攻击:                                                            │
│  ┌────────┐       ┌────────┐       ┌────────┐                         │
│  │ Client │ ◀───▶ │ Attacker│ ◀───▶ │ Server │                         │
│  └────────┘       │ (MITM) │       └────────┘                         │
│       │          └────────┘            │                               │
│       │              │                 │                               │
│       │    ① 截获请求               ③ 转发请求                        │
│       │    ② 读取/修改数据           ④ 截获响应                        │
│       │    ⑤ 修改/转发给客户端                                         │
│                                                                         │
│  危害:                                                                  │
│  • 窃取敏感信息 (密码、Token、信用卡)                                   │
│  • 篡改数据 (注入恶意代码)                                              │
│  • 会话劫持                                                             │
│  • 钓鱼攻击                                                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

1.2 常见的 MITM 攻击方式

┌─────────────────────────────────────────────────────────────────────────┐
│                     常见 MITM 攻击方式                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. ARP 欺骗 (局域网)                                                   │
│  ─────────────────────                                                  │
│  攻击者发送伪造的 ARP 响应,将网关 IP 绑定到自己的 MAC 地址              │
│                                                                         │
│  正常: 网关 192.168.1.1 → MAC_Gateway                                   │
│  攻击: 网关 192.168.1.1 → MAC_Attacker  (伪造)                          │
│                                                                         │
│  受害者的所有流量都会经过攻击者                                          │
│                                                                         │
│  2. DNS 劫持                                                            │
│  ─────────────                                                          │
│  篡改 DNS 响应,将域名解析到攻击者服务器                                 │
│                                                                         │
│  正常: bank.com → 1.2.3.4 (真实服务器)                                  │
│  攻击: bank.com → 5.6.7.8 (钓鱼服务器)                                  │
│                                                                         │
│  3. Wi-Fi 钓鱼 (Evil Twin)                                              │
│  ─────────────────────────                                              │
│  创建同名的假 Wi-Fi 热点,诱导用户连接                                   │
│                                                                         │
│  真实: Starbucks_WiFi (官方)                                            │
│  钓鱼: Starbucks_WiFi (攻击者控制,信号更强)                             │
│                                                                         │
│  4. SSL 剥离 (SSL Stripping)                                            │
│  ───────────────────────────                                            │
│  强制将 HTTPS 降级为 HTTP                                               │
│                                                                         │
│  用户 ──HTTP──▶ 攻击者 ──HTTPS──▶ 服务器                               │
│                                                                         │
│  用户看到的是 HTTP,攻击者可以读取明文                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2. HTTPS 为什么能防止 MITM

2.1 HTTPS 握手流程

┌─────────────────────────────────────────────────────────────────────────┐
│                       TLS 1.2 握手流程                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│     Client                                          Server              │
│        │                                               │                │
│        │─────── ClientHello ─────────────────────────▶│                │
│        │        (支持的加密套件、随机数 A)              │                │
│        │                                               │                │
│        │◀────── ServerHello ──────────────────────────│                │
│        │        (选定的加密套件、随机数 B)              │                │
│        │                                               │                │
│        │◀────── Certificate ──────────────────────────│                │
│        │        (服务器证书)                           │                │
│        │                                               │                │
│        │◀────── ServerHelloDone ─────────────────────│                │
│        │                                               │                │
│        │        [客户端验证证书]                        │                │
│        │        1. 检查证书是否由受信任 CA 签发          │                │
│        │        2. 检查证书是否过期                      │                │
│        │        3. 检查域名是否匹配                      │                │
│        │                                               │                │
│        │─────── ClientKeyExchange ───────────────────▶│                │
│        │        (用服务器公钥加密的预主密钥)            │                │
│        │                                               │                │
│        │─────── ChangeCipherSpec ────────────────────▶│                │
│        │                                               │                │
│        │─────── Finished (加密) ─────────────────────▶│                │
│        │                                               │                │
│        │◀────── ChangeCipherSpec ─────────────────────│                │
│        │                                               │                │
│        │◀────── Finished (加密) ──────────────────────│                │
│        │                                               │                │
│        │◀═══════ 加密通信 ═══════════════════════════▶│                │
│                                                                         │
│   会话密钥 = PRF(预主密钥, 随机数A, 随机数B)                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

2.2 证书链验证

┌─────────────────────────────────────────────────────────────────────────┐
│                         证书链验证                                       │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  证书链结构:                                                            │
│  ───────────                                                            │
│                                                                         │
│  ┌─────────────────────┐                                               │
│  │   Root CA 证书       │  ← 预装在操作系统/浏览器中                     │
│  │   (自签名)          │  ← 信任锚点                                    │
│  └──────────┬──────────┘                                               │
│             │ 签发                                                      │
│             ▼                                                          │
│  ┌─────────────────────┐                                               │
│  │   中间 CA 证书       │  ← 由 Root CA 签发                            │
│  │                     │                                               │
│  └──────────┬──────────┘                                               │
│             │ 签发                                                      │
│             ▼                                                          │
│  ┌─────────────────────┐                                               │
│  │   服务器证书         │  ← 由中间 CA 签发                             │
│  │   (example.com)     │  ← 包含域名和公钥                              │
│  └─────────────────────┘                                               │
│                                                                         │
│  验证过程:                                                              │
│  ─────────                                                              │
│  1. 服务器发送: 服务器证书 + 中间 CA 证书                                │
│  2. 浏览器用中间 CA 公钥验证服务器证书签名                               │
│  3. 浏览器用 Root CA 公钥验证中间 CA 证书签名                            │
│  4. Root CA 在信任列表中 → 验证通过                                     │
│                                                                         │
│  如果任何一步验证失败:                                                   │
│  • 签名不匹配                                                           │
│  • 证书过期                                                             │
│  • 域名不匹配                                                           │
│  • CA 不受信任                                                          │
│  → 浏览器显示安全警告                                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

3. Charles/Fiddler 如何抓取 HTTPS

3.1 抓包工具的 MITM 原理

┌─────────────────────────────────────────────────────────────────────────┐
│               Charles 抓取 HTTPS 的原理                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  前提条件:                                                              │
│  ─────────                                                              │
│  用户必须手动安装并信任 Charles 的根证书                                 │
│                                                                         │
│  步骤详解:                                                              │
│  ─────────                                                              │
│                                                                         │
│  ┌────────┐        ┌─────────┐        ┌────────┐                       │
│  │ Browser│        │ Charles │        │ Server │                       │
│  └────┬───┘        └────┬────┘        └───┬────┘                       │
│       │                 │                  │                            │
│       │  ① ClientHello │                  │                            │
│       │────────────────▶│                  │                            │
│       │                 │  ② ClientHello  │                            │
│       │                 │─────────────────▶│                            │
│       │                 │                  │                            │
│       │                 │  ③ 真实证书      │                            │
│       │                 │◀─────────────────│                            │
│       │                 │                  │                            │
│       │  ④ 伪造证书    │                  │                            │
│       │◀────────────────│  (Charles 动态   │                            │
│       │  (域名相同,     │   生成,用自己   │                            │
│       │   Charles CA   │   的 CA 签发)    │                            │
│       │   签发)        │                  │                            │
│       │                 │                  │                            │
│       │  ⑤ 验证证书 ✅  │                  │                            │
│       │  (Charles CA   │                  │                            │
│       │   已被信任)    │                  │                            │
│       │                 │                  │                            │
│       │  ⑥ 发送数据     │                  │                            │
│       │════════════════▶│  ⑦ 解密 + 转发   │                            │
│       │  (TLS 1)       │════════════════▶│                            │
│       │                 │  (TLS 2)        │                            │
│       │                 │                  │                            │
│       │  ⑨ 返回数据     │  ⑧ 响应数据      │                            │
│       │◀════════════════│◀════════════════│                            │
│       │  (重新加密)    │  (解密查看)     │                            │
│                                                                         │
│  关键点:                                                                │
│  • Charles 建立两条独立的 TLS 连接                                      │
│  • 浏览器 ◀─TLS─▶ Charles ◀─TLS─▶ 服务器                              │
│  • Charles 能看到明文数据                                               │
│  • 如果用户未信任 Charles CA,浏览器会报错                               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

3.2 Charles 证书安装流程

bash
# macOS 安装 Charles 根证书
# 1. Charles → Help → SSL Proxying → Install Charles Root Certificate
# 2. 钥匙串访问 → 系统 → 找到 Charles Proxy CA
# 3. 双击 → 信任 → 始终信任

# iOS 安装
# 1. 配置代理指向 Charles
# 2. Safari 访问 chls.pro/ssl
# 3. 设置 → 通用 → VPN与设备管理 → 安装证书
# 4. 设置 → 通用 → 关于本机 → 证书信任设置 → 启用完全信任

# Android 安装 (需要 root 或 7.0 以下)
# Android 7.0+ 默认不信任用户安装的 CA 证书
# 需要: 1) root 安装到系统 CA  2) APP 配置 network-security-config

3.3 为什么 Charles 能抓包,黑客不能?

┌─────────────────────────────────────────────────────────────────────────┐
│                Charles vs 真实攻击者                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Charles (合法抓包):                                                    │
│  ───────────────────                                                    │
│  ✅ 用户主动安装并信任 Charles 根证书                                    │
│  ✅ Charles 用自己的 CA 签发伪证书                                      │
│  ✅ 浏览器信任该 CA → 验证通过                                          │
│  ✅ 用户知道自己在做什么                                                 │
│                                                                         │
│  攻击者 (非法 MITM):                                                    │
│  ─────────────────────                                                  │
│  ❌ 攻击者的 CA 不在系统信任列表中                                       │
│  ❌ 伪造的证书无法通过验证                                               │
│  ❌ 浏览器显示安全警告: "您的连接不是私密连接"                            │
│  ❌ 用户如果忽略警告继续访问,才会被攻击                                  │
│                                                                         │
│  关键区别:                                                              │
│  ─────────                                                              │
│  合法的 CA (DigiCert, Let's Encrypt 等) 不会为攻击者签发证书             │
│  攻击者只能用自签名证书,浏览器不信任                                    │
│                                                                         │
│  例外情况 (真实威胁):                                                    │
│  ─────────────────                                                      │
│  1. 企业内网: IT 部门在员工电脑安装了监控 CA                             │
│  2. 恶意软件: 安装了恶意 CA 到用户系统                                   │
│  3. CA 被入侵: 历史上有 CA 被入侵签发恶意证书 (DigiNotar)                │
│  4. 国家级攻击: 某些国家强制安装政府 CA                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

4. 前端如何防护 MITM

4.1 HSTS (HTTP Strict Transport Security)

http
# 服务器响应头
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
┌─────────────────────────────────────────────────────────────────────────┐
│                         HSTS 防护原理                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  没有 HSTS (可被 SSL 剥离攻击):                                          │
│  ────────────────────────────                                           │
│  用户输入: bank.com                                                     │
│       ↓                                                                 │
│  浏览器请求: http://bank.com                                            │
│       ↓                                                                 │
│  攻击者拦截,不转发 HTTPS 重定向                                         │
│       ↓                                                                 │
│  用户一直使用 HTTP (明文)                                               │
│                                                                         │
│  有 HSTS:                                                               │
│  ────────                                                               │
│  用户输入: bank.com                                                     │
│       ↓                                                                 │
│  浏览器检查 HSTS 缓存                                                   │
│       ↓                                                                 │
│  强制使用 https://bank.com (不发 HTTP 请求)                             │
│       ↓                                                                 │
│  即使攻击者在中间也无法降级                                              │
│                                                                         │
│  HSTS Preload List:                                                     │
│  ──────────────────                                                     │
│  Chrome/Firefox 内置的 HSTS 站点列表                                    │
│  首次访问就强制 HTTPS,无需先访问一次                                    │
│  提交: https://hstspreload.org                                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

4.2 证书固定 (Certificate Pinning)

javascript
// 原理: 客户端预置服务器证书的公钥指纹,拒绝其他证书

// 方案 1: Public-Key-Pins (已废弃,风险太高)
// 方案 2: 客户端代码内置校验

// React Native / 移动端示例
const pinnedCertificates = {
    'api.example.com': [
        'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', // 主证书
        'sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=', // 备份证书
    ],
};

// 请求时校验 (示例,实际实现依赖平台)
async function secureFetch(url) {
    const response = await fetch(url);
    
    // 获取证书指纹 (需要平台支持)
    const certFingerprint = await getCertificateFingerprint(url);
    const hostname = new URL(url).hostname;
    
    if (!pinnedCertificates[hostname]?.includes(certFingerprint)) {
        throw new Error('Certificate pinning failed');
    }
    
    return response;
}
xml
<!-- Android network-security-config.xml -->
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2025-01-01">
            <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
            <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

4.3 前端检测 MITM

javascript
// 方法 1: 检测证书是否被替换
async function detectMITM() {
    try {
        const response = await fetch('/api/cert-check');
        const serverCertHash = response.headers.get('X-Cert-Hash');
        
        // 预置的正确证书哈希
        const expectedHash = 'sha256:abc123...';
        
        if (serverCertHash !== expectedHash) {
            console.error('Possible MITM detected!');
            // 上报、告警、阻止敏感操作
        }
    } catch (e) {
        // 证书验证失败
    }
}

// 方法 2: 利用 Service Worker 检测
// 如果 HTTPS 降级为 HTTP,Service Worker 无法注册
if ('serviceWorker' in navigator && location.protocol === 'https:') {
    navigator.serviceWorker.register('/sw.js')
        .then(() => console.log('HTTPS verified'))
        .catch(() => console.error('Possible downgrade attack'));
}

// 方法 3: 监控 Security 状态
if (window.isSecureContext === false) {
    console.error('Not in secure context!');
}

4.4 敏感操作二次加密

javascript
// 即使 HTTPS 被破解,敏感数据仍然加密
import { box, randomBytes } from 'tweetnacl';
import { encodeBase64, decodeBase64 } from 'tweetnacl-util';

// 服务端公钥 (内置在代码中)
const serverPublicKey = decodeBase64('服务器公钥');

function encryptSensitiveData(data) {
    // 生成临时密钥对
    const clientKeyPair = box.keyPair();
    const nonce = randomBytes(box.nonceLength);
    
    // 加密数据
    const message = new TextEncoder().encode(JSON.stringify(data));
    const encrypted = box(message, nonce, serverPublicKey, clientKeyPair.secretKey);
    
    return {
        ciphertext: encodeBase64(encrypted),
        nonce: encodeBase64(nonce),
        clientPublicKey: encodeBase64(clientKeyPair.publicKey),
    };
}

// 登录时加密密码
async function login(username, password) {
    const encryptedPassword = encryptSensitiveData({ password });
    
    return fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({
            username,
            ...encryptedPassword,
        }),
    });
}

5. 真实案例分析

案例 1: DigiNotar CA 被入侵 (2011)

┌─────────────────────────────────────────────────────────────────────────┐
│                    DigiNotar 事件                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  事件经过:                                                              │
│  ─────────                                                              │
│  1. 黑客入侵荷兰 CA DigiNotar                                           │
│  2. 签发了 500+ 伪造证书,包括 *.google.com                             │
│  3. 伊朗政府使用这些证书监控公民 Gmail                                  │
│  4. 约 30 万 IP 受影响                                                  │
│                                                                         │
│  攻击方式:                                                              │
│  ─────────                                                              │
│  ISP 级别 DNS 劫持 + 伪造的 Google 证书                                 │
│  用户访问 Gmail 时,DNS 解析到攻击者服务器                               │
│  攻击者使用 DigiNotar 签发的伪证书,浏览器不报警                         │
│                                                                         │
│  后果:                                                                  │
│  ──────                                                                 │
│  • DigiNotar 被所有浏览器吊销信任,公司破产                              │
│  • Chrome 引入证书透明度 (Certificate Transparency)                     │
│  • 浏览器加强证书吊销检查                                               │
│                                                                         │
│  教训:                                                                  │
│  ──────                                                                 │
│  • CA 是信任链的关键,一旦失守,HTTPS 形同虚设                          │
│  • 证书固定 (Pinning) 可以防御此类攻击                                  │
│  • Google 后来实施了公钥固定 (HPKP,现已废弃)                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

案例 2: 企业 HTTPS 监控

┌─────────────────────────────────────────────────────────────────────────┐
│                   企业 HTTPS 监控                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  场景:                                                                  │
│  ──────                                                                 │
│  很多企业会在员工电脑安装企业 CA,监控 HTTPS 流量                        │
│  用于防止数据泄露、检测恶意软件                                          │
│                                                                         │
│  实现方式:                                                              │
│  ─────────                                                              │
│  1. IT 通过组策略在员工电脑安装企业根 CA                                 │
│  2. 企业防火墙/代理充当 MITM                                            │
│  3. 员工所有 HTTPS 流量都经过企业代理解密检查                            │
│                                                                         │
│  前端开发者注意:                                                        │
│  ──────────────                                                         │
│  • 在企业网络中测试时,证书可能是企业 CA 签发的                          │
│  • 部分 API 可能因证书固定失败                                          │
│  • 某些安全测试结果可能不准确                                            │
│                                                                         │
│  检测方法:                                                              │
│  ─────────                                                              │
│  // 在控制台检查证书颁发者                                              │
│  // Chrome DevTools → Security → View certificate                       │
│  // 如果 Issuer 不是知名 CA,可能在被监控                               │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

案例 3: 12306 自签名证书

┌─────────────────────────────────────────────────────────────────────────┐
│                  12306 证书问题 (历史)                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  历史情况 (2010s):                                                      │
│  ─────────────────                                                      │
│  12306 使用 SRCA (中铁数字证书认证中心) 签发的证书                       │
│  SRCA 不在主流浏览器信任列表中                                          │
│  用户需要手动下载安装 SRCA 根证书                                       │
│                                                                         │
│  问题:                                                                  │
│  ──────                                                                 │
│  1. 用户访问时看到 "不安全" 警告                                        │
│  2. 手动安装证书的做法培养了忽视安全警告的习惯                           │
│  3. 如果有钓鱼网站,用户可能也会忽视警告                                 │
│                                                                         │
│  现状 (2020s):                                                          │
│  ─────────────                                                          │
│  12306 已切换到 DigiCert 等国际 CA 签发的证书                           │
│  访问时不再有安全警告                                                   │
│                                                                         │
│  教训:                                                                  │
│  ──────                                                                 │
│  不要引导用户安装非标准 CA,这会:                                       │
│  • 降低用户安全意识                                                     │
│  • 增加钓鱼攻击风险                                                     │
│  • 影响用户体验                                                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

6. 开发调试指南

6.1 正确使用 Charles/Fiddler

bash
# 1. 仅在开发环境使用
# 2. 不要在生产设备上安装抓包工具的 CA
# 3. 测试完成后移除信任的 CA

# Charles 配置最佳实践
# Proxy → SSL Proxying Settings
# 仅添加需要抓包的域名,不要用 *

# 示例配置:
# Include:
#   - api.example.com:443
#   - staging.example.com:443
# 不要添加:
#   - *:* (会抓所有 HTTPS,包括银行、邮箱)

6.2 调试证书问题

javascript
// 浏览器端检查证书信息
// Chrome DevTools → Security 面板

// Node.js 检查证书
const https = require('https');
const tls = require('tls');

function checkCertificate(hostname, port = 443) {
    return new Promise((resolve, reject) => {
        const socket = tls.connect(port, hostname, {
            servername: hostname,
        }, () => {
            const cert = socket.getPeerCertificate();
            console.log('Subject:', cert.subject);
            console.log('Issuer:', cert.issuer);
            console.log('Valid From:', cert.valid_from);
            console.log('Valid To:', cert.valid_to);
            console.log('Fingerprint:', cert.fingerprint256);
            socket.end();
            resolve(cert);
        });
        
        socket.on('error', reject);
    });
}

checkCertificate('example.com').then(console.log);
bash
# OpenSSL 检查证书
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | \
    openssl x509 -noout -text

# 检查证书链
openssl s_client -connect example.com:443 -showcerts

7. 面试高频问题

Q1: Charles 为什么能抓取 HTTPS?

  1. 用户手动安装并信任 Charles 根证书
  2. Charles 动态为每个域名生成伪造证书,用自己的 CA 签发
  3. 浏览器信任该 CA,验证通过
  4. Charles 建立两条独立的 TLS 连接,在中间解密查看

Q2: 普通 MITM 攻击者为什么不能像 Charles 一样?

攻击者的 CA 不在系统信任列表中,伪造的证书无法通过浏览器验证,会显示安全警告。除非:

  • 用户忽略警告继续访问
  • 攻击者在用户设备安装了恶意 CA
  • 合法 CA 被入侵签发了恶意证书

Q3: 如何防止 SSL 剥离攻击?

  1. 启用 HSTS,强制浏览器使用 HTTPS
  2. 加入 HSTS Preload List,首次访问就强制 HTTPS
  3. 使用 HTTPS-only 模式 (现代浏览器支持)

Q4: 证书固定 (Pinning) 的优缺点?

优点:

  • 防止被入侵的 CA 签发恶意证书
  • 额外的安全层

缺点:

  • 证书更新时需要同步更新客户端
  • 配置错误可能导致服务不可用
  • 维护成本高

Q5: 企业环境下如何检测 HTTPS 是否被监控?

  1. 检查证书颁发者 (Issuer) 是否为知名 CA
  2. 使用不同网络 (如手机热点) 对比证书指纹
  3. 访问 https://www.howsmyssl.com 等检测网站

前端面试知识库