HTTPS 中间人攻击:原理、实践、为什么 HTTPS 还不够
摘要
本文指出 HTTPS 并非绝对安全,中间人攻击(MITM)常因实现不当被绕过。文章分析了 MITM 的原理及常见绕过手段(如弱证书校验、用户忽略警告)。作者通过实验发现,浏览器和移动 App 在证书验证上存在漏洞,极易被劫持。防御需依赖强制 HTTPS、HSTS、证书锁定及 VPN 等措施。结论是开发者必须严格实现安全协议,用户不应盲目信任“安全”的 App。
第一次知道 HTTPS 时觉得——这下网络安全了。后来才知道,HTTPS 拦不住中间人攻击,拦得住的只有"正确实现的 HTTPS"。此文是 3 篇相关文章读下来的笔记 + 我自己的验证。
什么是中间人攻击
MITM(Man-in-the-Middle):攻击者把自己塞到客户端和服务端中间,两边的通信都要经过他。
两种玩法:
被动:只监听,不改内容
主动:拦截 + 篡改,改完再转发
危害:账号密码、cookie、支付信息、聊天记录——全部裸奔。
HTTPS 怎么挡 MITM
HTTPS = HTTP + TLS。TLS 干三件事:
加密:传输内容用对称加密(AES),第三方看不懂
认证:服务端发 X.509 证书证明自己是谁
完整性:用 MAC/HMAC 校验内容没被改
关键就在证书:客户端收到服务端证书时,会验证:
是不是受信任的 CA签发的
有没有过期
签发的域名和访问的域名是否一致
有没有被 CA 吊销(CRL/OCSP)
这 4 条全过,才认为对面是真的。任何一条不过,浏览器就报警。
但是:HTTPS 真的能挡吗?
问得好。理论上能挡,实践上经常被绕过。
绕过 1:弱证书校验
很多 App 写代码时偷懒,不验证证书,或者验证逻辑有 bug:
不检查证书是否过期
不检查证书域名
遇到自签名证书直接信任
实现自定义 TrustManager,把所有证书都当合法的
这种 App 用 HTTPS,等于没用。攻击者随便伪造一个证书就能劫持。
绕过 2:CA 被骗签发证书
历史上多次发生 CA 误签或被黑导致假证书流入市场(Symantec、 DigiNotar 都出过事)。攻击者拿到合法证书,浏览器看不出来。
绕过 3:用户主动忽略警告
浏览器弹"证书错误"对话框,用户点"继续访问"。这等于把整个 HTTPS 体系作废。
实战:在自己机器上复现一次 MITM
纸上得来终觉浅。我搭了一次完整的 MITM 环境,看看 HTTPS 在实践中到底有多脆弱。
准备工作:
一个域名(DV 证书网上能免费申请,Let's Encrypt 一把梭)
一台 nginx 服务器,配置该域名的 HTTPS
Fiddler + FreeHttp 插件,用来重定向流量
一台手机,被攻击对象
步骤 1:搭 nginx 中间人
这个 nginx 实际上就是中间人。当流量被引导到这里时,所有 HTTPS 都会和这台服务器建立 SSL 通道,而不是真正的目标。
步骤 2:用 Fiddler 重定向 CONNECT 目标
关键点:CONNECT 请求是明文的,在 SSL 通道建立之前。这时候改请求目标是可行的。
Fiddler + FreeHttp 插件,加规则:
还要关掉 "skip connect tunnels",让 Fiddler 把 CONNECT 转发到我们自己的服务器。
步骤 3:观察浏览器反应
用浏览器访问 https://www.baidu.com——
Chrome:直接报错,不能继续。因为 baidu.com 开了 HSTS,浏览器记录过"必须 HTTPS",证书不对直接禁止。
Edge:弹一个"证书错误"的对话框,询问是否继续。点继续就能访问——但用户已经被劫持了。
这就是为什么我前面说"HTTPS 拦不住用户主动忽略警告"。
移动 App:情况更糟
浏览器还算有底线,App 很多都没有。
同一个 MITM 环境,把手机连上 Fiddler 代理(不需要装证书),试着打开各种 App:
知乎 iOS
直接无视证书错误,和正常访问一样。所有浏览、发帖、登录的流量都被中间人解密。用户毫无察觉。
360 浏览器 iOS
更离谱——号称"安全"的浏览器,遇到证书错误只把绿色小盾牌变成灰色,没有任何询问,直接建立 SSL 通道。所有浏览内容裸奔。
其他
部分金融 App、部分 App 的部分模块,都存在类似问题。开发者忽视了证书校验,或者对错误处理不当。
教训:App 是不是真安全,光看代码看不出来的,要拿 MITM 工具实际验证。
MITM 的攻击向量
上面实验的"重定向 CONNECT"在真实攻击里很少见。更常见的 MITM 攻击路径:
ARP 欺骗:内网里伪造网关 MAC,所有流量先过我
DNS 劫持:把目标域名解析到攻击者 IP,改 hosts 文件就能模拟
WPAD 劫持:自动代理配置被篡改
BGP 路由劫持:网络层劫持,国家级手段
恶意 Wi-Fi:公共热点直接 ARP 欺骗
这些都不需要用户连接任何代理,链路上的网络设备就能做。所以"别连不认识的 Wi-Fi"不是开玩笑。
为什么 HTTPS 不够 + 还要这些
有人会问:我用 RSA 加密所有请求不就行了,为什么一定要 HTTPS?
答案是:HTTPS = 加密 + 身份认证 + 完整性,三个一起。只用 RSA 加密:
密钥交换麻烦:每对通信双方都要换密钥,管理噩梦
没有身份认证:客户端不知道对面是谁,攻击者替换 RSA 公钥就破功
性能差:RSA 比 AES 慢两个数量级,不适合大数据
无标准:每个团队一套实现,互相不通
HTTPS 的优雅之处:握手时用 RSA/ECDHE 交换密钥,之后用 AES 加密数据。两边的好处都占了。
怎么真拦住 MITM
几个层次:
1. 强制 HTTPS + HSTS
服务端返回 Strict-Transport-Security: max-age=31536000,告诉浏览器"以后必须 HTTPS"。浏览器记下,下次证书不对直接拒绝,不给用户"继续访问"的选项。
2. Certificate Pinning
App 里硬编码目标服务端的证书指纹(或公钥),只信任这个。CA 都被骗了也用不了。
代价:证书更新要发版,运维麻烦。所以一般只对核心 API 做 pinning。
3. DNS 层
DNSSEC:DNS 响应签名,防 DNS 劫持
DoH / DoT:DNS 走加密通道,中间人看不见
4. 网络层
VPN:所有流量走加密隧道,公网 Wi-Fi 上必备
不连陌生 Wi-Fi:老生常谈,但真有用
5. 客户端正确实现
这一点最重要但最容易被忽略:
用系统 TLS 库,不要自己写
不要禁用证书验证
不要信任自签名证书(除非是预置的)
遇到错误不要静默"重试"或"忽略"
我自己的部署习惯
服务端:
所有站点强制 HTTPS(nginx 301)
开 HSTS,max-age 一年
证书用 Let's Encrypt 自动续期
禁用过时的 TLS 版本(只开 TLS 1.2 / 1.3)
客户端:
App 不写自定义 TrustManager,用系统默认
关键 API 加 certificate pinning
出错就报错,不静默重试
网络:
公共 Wi-Fi 上一定开 VPN
家里 DNS 走 DoH
路由器强制管理页面 HTTPS
写在最后
MITM 攻击的可怕之处不在技术,在用户的"无感"。
你看到 HTTPS 标志、看到绿色小盾牌、看到 App 正常显示内容——但这一切都可能是中间人在演戏。安全的责任不在用户,在开发者。
读完那 3 篇文章最深的感受:别相信任何说自己"安全"的 App,除非你亲手用 MITM 工具验证过证书校验是真生效的。
这个时代,"实现了 HTTPS" 离 "HTTPS 真起作用" 还有 100 公里。
