HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 代理技术全栈手册

    • 代理技术全栈手册 - HiHuo
    • 原理篇

      • 第01章 代理是什么:正向 / 反向 / 透明 / 隧道的统一模型
      • 第02章 代理与网络层级:L3 / L4 / L5 / L7 在哪里截断流量
      • 第03章 一个请求穿过代理的一生:连接生命周期全景
    • 协议篇

      • 第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
      • 第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS
      • 第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析
      • 第07章 HTTP/2、gRPC 与 HTTP/3(QUIC) 代理的挑战
      • 第08章 代理自动配置:PAC / WPAD / 系统代理 / NO_PROXY
    • 层级与转发篇

      • 第09章 L4 代理:TCP/UDP 转发与连接级负载均衡
      • 第10章 L7 代理:协议感知与基于内容的路由
      • 第11章 透明代理:iptables REDIRECT/DNAT、TPROXY 与 eBPF 劫持
      • 第12章 数据搬运的艺术:splice / sendfile / 零拷贝 / io_uring
    • 组件横评篇

      • 第13章 Nginx / OpenResty:反向代理、upstream 与 Lua 可编程
      • 第14章 HAProxy:L4/L7、ACL、健康检查与 stick table
      • 第15章 Envoy:xDS 动态配置与 filter chain,为何是云原生数据面
      • 第16章 Traefik / Caddy:自动服务发现与自动 HTTPS
      • 第17章 Squid 与正向/缓存代理:企业出网、缓存与审计
      • 第18章 mitmproxy:抓包、改包、脚本化调试
      • 第19章 内网穿透与隧道:frp / gost / SSH 隧道 / ngrok
      • 第20章 科学上网生态的技术原理(技术中立)
    • 多语言手写篇

      • 第21章 Go:100 行手写 HTTP/CONNECT + SOCKS5 代理
      • 第22章 Rust:基于 tokio 的高性能 TCP 代理
      • 第23章 Python:asyncio 实现,适合调试与脚本
      • 第24章 C:epoll 裸写与零拷贝,及语言选型对比
    • 容器与K8s篇

      • 第25章 Docker 里的代理:HTTP_PROXY、build/pull 与 daemon 配置
      • 第26章 Sidecar 与流量劫持:Istio init-container 的 iptables 原理
      • 第27章 Ingress 与南北流量:Ingress-nginx 与 Gateway API
      • 第28章 Egress 与出网治理:出口网关、registry mirror、审计
      • 第29章 Service Mesh 数据面:Envoy Sidecar 全链路
    • 进阶篇

      • 第30章 可编程代理:Lua / Wasm / eBPF / xDS,代理的"软件定义"
      • 第31章 性能调优:并发模型、连接池、超时与重试、压测
      • 第32章 排错决策树:502 / 504 / 握手失败 / 环路 / 泄漏
      • 第33章 代理安全:开放代理、SSRF、凭证泄漏与攻击面
    • 底层机制篇

      • 第34章 代理的背压与流控:一个代理最难的部分
      • 第35章 socket 与 TCP 状态机:半关闭、超时、连接生命周期
      • 第36章 HTTP/2 帧、流控与 HPACK:h2 代理的内部机制
      • 第37章 负载均衡算法推导与韧性状态机
      • 第38章 Capstone:把玩具代理改造成生产级骨架
    • 综合实战篇

      • 第39章 企业多跳转发链:拓扑、协议矩阵与贯穿性难题
      • 第40章 端到端实战:把 6 类流量全代理通
      • 第41章 更刁钻的流量:gRPC、长轮询、WebRTC、大文件、双向流
      • 第42章 可落地完整参考实现:一套能跑的多协议转发栈
    • 附录

      • 附录 A:代理协议报文速查(HTTP / SOCKS / PAC / PROXY protocol)
      • 附录 B:组件选型决策树
      • 附录 C:抓包与命令速查

第05章 HTTPS 与 TLS 代理:终止 / 透传 / MITM / SNI / mTLS

学习目标

  • 掌握代理处理 TLS 的四种方式:透传、终止、重加密、MITM,及各自的信任前提
  • 理解 MITM 为什么需要"客户端信任代理的根证书",以及 HSTS/证书锁定如何反制
  • 搞懂 SNI 在选证书与路由中的双重角色,及 ECH 带来的变局
  • 理解 mTLS(双向 TLS)及它在 Service Mesh 里的位置
  • 用 nginx ssl_preread(透传)和 mitmproxy(MITM)亲手验证

前置知识

  • 第04章 HTTP 代理协议(CONNECT 隧道)、第02章 SNI 嗅探
  • 网络手册 · 应用层协议(TLS 握手)

原理

TLS 是加密的,这给代理出了道难题:想处理内容就得解密,解密就得有证书,有证书就涉及信任。 围绕"解不解密、谁信任谁",代理有四种玩法:

①  透传 Passthrough     Client ══TLS══▶ [代理盲转密文] ══TLS══▶ Origin      代理看不到明文
②  终止 Termination     Client ══TLS══▶ [代理解密] ──明文──▶ Origin         客户端的TLS到代理为止
③  重加密 Re-encrypt    Client ══TLS══▶ [代理解密再加密] ══TLS'══▶ Origin   两段独立TLS
④  中间人 MITM          Client ══TLS(假证书)══▶ [代理解密偷看] ══TLS══▶ Origin  代理假冒源站

① TLS 透传(Passthrough)——不解密,盲转

代理把 TLS 当不透明字节流,原样转发,全程不持有任何证书。这正是 第04章 的 CONNECT 隧道,以及 第02章 的 SNI 嗅探路由(偷看明文 SNI 选后端,但不解密)。

  • 优点:端到端加密不被破坏,代理无需证书,性能高(可 splice)
  • 局限:代理看不到 URL/Header,做不了 L7 的事(缓存、改写、按 path 路由)
  • 用在:反向代理按域名分流 HTTPS 又不想解密、TCP 直通

② TLS 终止(Termination / Offload)——解密成明文

代理终止客户端的 TLS:客户端与代理之间是加密的,代理解密成明文后处理,再转发给后端。这是反向代理的标配。

Client ══TLS══▶ [Nginx 持有 example.com 证书+私钥,解密] ──HTTP 明文──▶ 后端
  • 前提:代理必须持有该域名的真实证书 + 私钥(合法持有,因为代理就是服务端的门面)
  • 好处:把 TLS 计算从后端卸载到代理、集中管理证书、能做全套 L7 处理
  • 注意:代理→后端如果是明文,要确保这段在可信网络内(否则内网也该加密,见下)
  • 终止后,代理用 X-Forwarded-Proto: https 告诉后端"原始请求是 HTTPS"——否则后端以为是明文 http,可能生成错误的跳转/Cookie(第04章 转发头)

③ 重加密(Re-encryption)——两段独立 TLS

终止后到后端再开一条新 TLS。客户端↔代理一段 TLS,代理↔后端另一段 TLS,两段独立。

  • 用在:零信任网络(内网也全程加密)、合规要求、后端只接受 HTTPS
  • 代价:两次 TLS 计算;代理需同时校验后端证书

④ MITM(中间人)——代理假冒源站偷看明文

这是正向代理想看 HTTPS 明文时的唯一办法。代理动态假冒源站:为每个访问的域名现场签发一张伪造证书,骗客户端"我就是 example.com",从而解密。

Client ──CONNECT example.com:443──▶ [代理]
        ◀── 代理用自己的 CA 现签一张 example.com 证书,假装是源站 ──
Client ══TLS(信了假证书)══▶ [代理解密看明文] ══真TLS══▶ example.com
  • 关键前提:客户端必须信任代理的 CA 根证书(手动安装到信任库)。否则客户端会报"证书不可信"——这正是 MITM 的安全边界:没有你的同意(装根证书),别人 MITM 不了你的 HTTPS。
  • 用在:调试抓包(mitmproxy,第18章)、企业上网行为审计与 DLP、恶意场景
  • 反制:
    • HSTS:网站声明"只接受真证书",浏览器拒绝任何证书异常,MITM 失败
    • 证书锁定(Certificate Pinning):App 内置源站证书指纹,伪造证书过不了,移动端 App 常用——这也是为什么很多 App 在开了代理抓包后连不上
    • CT(Certificate Transparency):伪造证书未上 CT 日志会被发现

SNI 的双重角色

SNI(ClientHello 里的明文域名)干两件事:

  1. 让服务器选对证书:一个 IP 上托管多个 HTTPS 站点(SNI 之前做不到),靠 SNI 区分
  2. 让 L4 代理路由:不解密就能按域名分流(第02章 的 SNI 嗅探)

ECH(Encrypted Client Hello) 把 SNI 也加密了,意味着:基于 SNI 的路由和审计会失效,企业/运营商的"看域名"能力被削弱——这是当前 TLS 代理领域最大的变量。

mTLS(双向 TLS)——客户端也要出证书

普通 TLS 只有服务端出证书(客户端验证服务端)。mTLS 要求双方都出证书互相验证,实现"零信任"——只有持合法证书的服务才能互相通信。

  • 代理的角色:作为 mTLS 的终止/发起方。Service Mesh 的精髓就在这:Envoy sidecar 之间自动 mTLS,业务代码无感知,平台层就拿到了"服务间双向认证 + 加密"。详见 第26章、第29章。

TLS 握手状态机与会话复用(TLS 代理的性能命门)

代理做 TLS 终止/MITM 时,性能大头是握手。理解它才能优化:

完整握手(开销最大,含非对称运算):

  • TLS 1.2:2-RTT(ClientHello → ServerHello+证书 → 密钥交换 → Finished)
  • TLS 1.3:1-RTT(合并若干步),更快

每次完整握手都要做 ECDHE 密钥交换 + 证书验证;MITM 代理还要为每个新 SNI 现场签发证书(一次 ECDSA/RSA 签名),CPU 成本可观——这是 MITM/终止型代理的主要开销来源。

会话复用——避免重复完整握手,是 TLS 代理吞吐的命门:

机制做法服务端状态
Session ID(TLS1.2)服务端缓存会话,客户端带 ID 复用有状态(缓存)
Session Ticket(TLS1.2, RFC 5077)会话加密成 ticket 交客户端保存无状态
PSK + 0-RTT(TLS1.3)用上次预共享密钥,甚至 0-RTT 直发数据无状态

完整握手 vs 复用握手吞吐可差一个数量级,生产代理必开 ticket/PSK。但两个坑:① 0-RTT 有重放风险,只能用于幂等请求(呼应 第10章);② 多副本代理要共享 ticket key,否则负载均衡换一台机器就复用失败、退回完整握手(这也是 TLS 代理横向扩展的隐藏成本)。

四种方式对比

方式代理需要证书吗看得到明文端到端加密典型场景
透传不需要❌✅ 完整反向 SNI 分流、TCP 直通
终止需要真证书✅仅客户端→代理反向代理、TLS 卸载
重加密真证书 + 验后端✅两段独立零信任内网
MITM需客户端信任其 CA✅❌ 被打断调试、审计、(恶意)

️ 实现 / 命令

实验一:TLS 透传 + SNI 路由(nginx stream,不解密)

# nginx.conf 的 stream 块——工作在 L4,按 SNI 分流但不解密
stream {
    map $ssl_preread_server_name $backend {
        api.example.com   10.0.0.11:443;
        web.example.com   10.0.0.12:443;
        default           10.0.0.10:443;
    }
    server {
        listen 443;
        ssl_preread on;        # 只读 ClientHello 的 SNI,不解密
        proxy_pass $backend;   # 整条 TLS 连接盲转给选中的后端
    }
}

ssl_preread on 是关键——nginx 偷看 SNI 选后端,但没有任何证书配置,因为它不解密。后端才持有证书、做真正的 TLS 终止。

实验二:TLS 终止(nginx 解密成明文转后端)

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/nginx/certs/example.com.crt;   # 代理持有真证书
    ssl_certificate_key /etc/nginx/certs/example.com.key;

    location / {
        proxy_pass http://127.0.0.1:8080;          # 明文转后端(TLS 卸载)
        proxy_set_header X-Forwarded-Proto https;  # 告诉后端原始是 HTTPS
    }
}

实验三:MITM 抓 HTTPS 明文(mitmproxy)

# 1. 启动 mitmproxy(默认听 8080,首次运行会生成自己的 CA)
mitmproxy --mode regular --listen-port 8080 &

# 2. 不装 CA 直接抓 → 客户端报证书错误(这是 MITM 的安全边界!)
curl -x http://127.0.0.1:8080 https://example.com/
# curl: (60) SSL certificate problem: unable to get local issuer certificate

# 3. 信任 mitmproxy 的 CA 后,才能解密(演示用,生产慎重)
curl -x http://127.0.0.1:8080 \
     --cacert ~/.mitmproxy/mitmproxy-ca-cert.pem \
     https://example.com/
# 200 —— 此时 mitmproxy 界面里能看到完整的 HTTPS 明文请求与响应

第 2 步的失败恰恰是好事:它证明没有你主动安装根证书,HTTPS 就 MITM 不了。第 3 步装了 CA 才成功,对应企业为何要在员工机器上预装根证书才能审计。

实验四:看代理给你的是不是伪造证书

# 看服务器证书的签发者。正常是公共 CA;若是 mitmproxy/企业 CA,就是被 MITM 了
openssl s_client -connect example.com:443 -proxy 127.0.0.1:8080 </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer
# 正常: issuer=C=US, O=Let's Encrypt, CN=R3
# 被MITM:issuer=CN=mitmproxy CA   ← 警报!

排错

现象根因解决
unable to get local issuer certificate代理在 MITM 但客户端没信任其 CA装 CA(调试);或这是攻击,别信
透传时 ssl_preread 拿不到 SNI客户端没发 SNI,或用了 ECH退回 IP/端口路由
TLS 终止后网站跳转成 http://后端不知原始是 https配 X-Forwarded-Proto https + 后端识别它
App 开代理就连不上(网页正常)App 做了证书锁定,拒绝伪造证书锁定无法绕过;用未锁定的测试构建
HSTS 站点 MITM 失败浏览器对 HSTS 域拒绝证书异常设计如此,无法绕过
mTLS 握手失败 certificate required客户端没出证书或证书不被信任检查客户端证书与代理的 CA 配置
终止后后端 TLS 校验失败重加密段后端证书不被代理信任配后端 CA 或(仅测试)跳过校验

本章小结

  • 代理处理 TLS 有四招:透传(不解密、可 SNI 路由)、终止(解密卸载,反向代理标配)、重加密(两段 TLS,零信任)、MITM(假证书偷看明文)。
  • MITM 的安全底线是客户端必须信任代理 CA;HSTS 和证书锁定能反制 MITM。
  • SNI 既选证书又供 L4 路由;ECH 会让 SNI 路由/审计失效。
  • mTLS 双向认证是 Service Mesh 零信任的核心,由 sidecar 代理自动完成。

下一章 第06章 SOCKS 协议,我们离开 HTTP 世界,逐字节拆解 SOCKS4/4a/5 与 UDP ASSOCIATE——第21章 手写的那个 SOCKS5,原理就在这里。

Prev
第04章 HTTP 代理协议:绝对 URI、CONNECT 隧道、转发头与连接池
Next
第06章 SOCKS 协议:SOCKS4/4a/5 与 UDP ASSOCIATE 报文级解析