2014年4月7日,一个叫 Neel Mehta 的漏洞谷歌安全研究员在 OpenSSL 的代码库里提交了一个修复补丁。没人想到,心脏这个补丁会引爆一场持续数周的出血互联网恐慌。漏洞的漏洞编号是 CVE-2014-0160,后来大家更习惯叫它——心脏出血。心脏
问题出在 OpenSSL 处理 TLS 心跳扩展(heartbeat extension)的出血那段代码里。按 RFC 6520 的漏洞设计,客户端发一个“心跳请求”,心脏里面带上一段数据和一个长度字段,出血服务端收到后应该原样把数据回传,漏洞确认连接还活着。心脏但 OpenSSL 的出血实现犯了一个低级到让人后背发凉的错误:它没有检查请求里声明的长度是否和实际数据长度一致。你发一个 1 字节的漏洞数据,却告诉它“我发了 65535 字节”,它就会老老实实从内存里读 65535 字节返回给你。
这意味着什么?服务器内存里那 64KB 的内容——可能是用户的密码、私钥、会话 cookie、信用卡号——就这么被打包送给了任何一个会发这种畸形心跳包的客户端。攻击者甚至不需要认证,不需要登录,只要一个 TCP 连接就能反复捞。每次捞 64KB,捞个几百次,内存里的敏感数据就拼得七七八八了。
我第一次读到这个漏洞细节的时候,正坐在一个咖啡馆里。旁边有人在用手机银行转账,我盯着屏幕上的代码片段,脑子里只有一个念头:这玩意儿要是被大规模利用,整个 HTTPS 的信任模型就是个笑话。你浏览器地址栏那个绿色小锁,锁了个寂寞。
事实也确实如此。漏洞公开后的几个小时里,GitHub 上就冒出了一堆扫描工具。有人写了个 Python 脚本,十几行代码,跑起来就能从存在漏洞的服务器上往外掏内存。我试过其中一个,对着一个测试服务器跑了几轮,捞回来的数据里混着 HTTP 头、用户名、甚至一段没头没尾的聊天记录。那种感觉很奇怪——你明明是在做安全研究,但手指敲命令的时候,心里清楚这跟入侵没多大区别。
更麻烦的是 OpenSSL 的普及程度。它几乎躺在所有跑 HTTPS 的服务器底下:Apache、Nginx、邮件服务器、VPN 网关,甚至你家里的路由器固件。当时有人估算,全球有超过 50 万台服务器受这个漏洞影响。Cloudflare 的工程师后来写文章说,他们当时紧急给所有边缘节点打补丁,一边打一边看着日志里那些来自世界各地的畸形心跳请求——攻击早就开始了,只是没人知道。
修复本身不复杂。补丁就几行,加一个长度校验的事。但善后才是噩梦。你得给服务器打补丁、重新编译 OpenSSL、重启服务,然后——最要命的一步——换掉所有可能泄露的私钥和证书。因为漏洞存在期间,攻击者可能已经把你服务器的私钥捞走了。不换密钥,你打了补丁也白搭,人家照样能解密你之前的流量。
很多公司没做这一步。或者做了,但做得拖拖拉拉。几个月后还有人在扫描互联网,发现大量服务器虽然打了补丁,但证书还是老的那一批。这就像你家门锁被撬过,你换了把新锁,但钥匙早就被人配了一把——你换锁有什么用?
心脏出血之后,OpenSSL 项目拿到了史上第一笔像样的捐款。Linux 基金会搞了个核心基础设施计划,专门给这种“全世界都在用但没人给钱”的开源项目输血。但说实话,这更像是事后找补。一个支撑着半个互联网加密安全的库,长期只有两三个全职维护者,其中一个人还累到公开说自己 burnout 了。这种故事在开源圈子里一点都不新鲜,只是心脏出血把它炸到了台面上。
我后来在好几个不同场合跟人聊起这个漏洞。做运维的会叹气,说那几天通宵打补丁打到手软;做安全的会冷笑,说这种错误低级得像个实习生写的;做开发的往往一脸茫然,问“OpenSSL 不是系统自带的吗,还要自己维护?”——你看,问题就在这。大家都觉得这东西“应该”是安全的,没人去想它到底安不安全。
心脏出血不是第一个,也不是最后一个。它只是把互联网基础设施里那层薄薄的体面撕开了一个口子。口子后面是:代码是人写的,人会犯错,而错误在互联网上的传播速度,比你打补丁的速度快得多。
```