摘要
- Cloudbleed 让 HTML 转换器越过缓冲区读取内存,把无关请求的片段写进响应,击穿了共享边缘中客户之间本应存在的界线。
- Cloudflare 可以用功能总开关和解析器修复阻止新的泄漏,但已经进入搜索缓存的片段和可能暴露的凭证,必须由另一套无法从补丁本身推断完成度的流程处理。
触发站点不是受害站点
2017 年 2 月 17 日,Google Project Zero 向 Cloudflare 报告,部分经其网络返回的页面夹带了本不属于该页面的数据。内容包括 Cookie、身份验证令牌、POST 正文以及其他进程内存片段,其中一些响应已经被搜索引擎缓存。
“Cloudbleed”这个名称很容易让人联想到 Heartbleed,但这里更关键的是多租户结构。发生错误的是为大量客户执行页面转换的共享边缘进程。某个站点的异常 HTML 可以触发缺陷,被复制进响应的相邻字节却可能来自另一个客户的请求。
因此,触发域名清单不能当作受害者名册。引出故障路径的站点与数据恰好位于相邻内存的站点扮演不同角色。客户甚至不必启用相关功能,也不必拥有异常 HTML;只要它的请求在错误时刻进入同一个进程,其数据就可能越界。
真正被破坏的安全约束不只是“解析器必须正确理解这一页”,而是“为一个租户执行的工作绝不能读取或发出另一个请求的内存”。共享工作进程、缓冲区和紧密调度原本带来效率;当这条约束失守,它们就变成了互不相干的信任域彼此接触的通道。
新缓冲方式唤醒了旧指针错误
Cloudflare 把缺陷定位到旧的 Ragel 生成代码。三项 HTML 转换功能会调用它:邮箱地址混淆、服务器端排除和自动 HTTPS 重写。缺少一次 fhold 操作,会让指针停在缓冲区末端;调用方随后把解析结果当成有效区间,复制超过预定边界的字节。
泄漏频率的陡升出现在新解析器 cf-html 部署之后。它改变了缓冲方式,使旧代码更频繁地遇到边界条件。Cloudflare 特别澄清:缺陷不在 Ragel 本身,也不在 cf-html 本身。新组件改变了旧代码的运行环境,把潜伏问题放大成了更常见的信息泄露。
这类变更最难审。组件审查会问新代码是否正确,边界审查还要问新代码打破了周围系统的哪些旧假设。安全上线不能只跑正常页面;还要在精确缓冲边缘做差分测试,用金丝雀检查响应的数据来源,并把新组件唤醒的遗留调用链也纳入崩溃分析。
用绝对时刻记录关闭顺序
第一种恢复权力在 Cloudflare 自己手中:先拿掉继续发出数据的功能路径。事故报告的 UTC 详细时间线写明,2 月 18 日 00:32 收到缺陷细节,01:19 在全球关闭邮箱地址混淆,04:24 关闭自动 HTTPS 重写,07:22 在全球部署带有 cf-html 总开关的补丁。服务器端排除没有自己独立的全球开关,工程师必须现场开发并部署。
这段顺序说明,总开关不是运维附属品,而是功能安全设计的一部分。没有开关的功能迫使团队在事故中先改代码。可逆的功能可以牺牲自己而保留无关代理服务;不可分离的功能会迫使运营者在扩大停机与继续暴露之间二选一。
Cloudflare 还表示,此路径没有泄露客户 SSL 私钥,因为 TLS 终止运行在另一个 NGINX 实例中。但发生泄漏的 HTTP 进程并非没有秘密:其中存在一把用于 Cloudflare 机器间加密的私钥和少量内部认证秘密。进程隔离保护了一个高后果类别,却没有把可触及秘密降为零;它缩小的是爆炸半径。
后续重写转向 Rust,利用安全代码中的内存边界检查阻断这一类越界访问。这比期待审查者发现每一个指针错误更接近可验证的本地约束。但 Rust 不能自动消除授权错误、外部不安全代码或错误的数据流设计;它缩小的是一大类实现风险,而不是全部风险。
比率是估算,不是人口普查
最初报告把最高暴露期定在 2 月 13 日至 18 日,估算大约每 330 万个请求中有一个可能泄漏,约为 0.00003%。后续量化估计,从 2016 年 9 月 22 日到 2017 年 2 月 18 日共有 1,242,071 次触发:2 月 13 日之前为 605,037 次,触发站点不足 180 个;最后五天为 637,034 次,涉及 6,457 个触发站点。
这些数字必须和证据限制一起阅读。详细请求日志只做了 1% 抽样,且仅保留 2 月 8 日至 18 日;更早的详细日志已经删除,早期估算依赖聚合分析与崩溃记录。Cloudflare 说在所审查记录中没有发现恶意利用证据。这只是有限记录内的观察,并不能证明从未有人发现或利用缺陷。
事故统计不能替遥测制造确定性。触发请求不等于独立受害者;全网平均概率无法表达某一片段的敏感程度;抽样日志中的阴性搜索也不能封闭历史事实。
补丁无法召回已经发出的字节
关闭功能终止了新污染响应,却无法撤回已经交付的页面、已经建立的搜索索引和已经复制的凭证。Cloudflare 报告,搜索爬虫占估算触发请求的一半以上,团队清除了 8 万多个唯一缓存页面,并在第三方缓存中发现与 150 位客户相关的数据。
这些数字都不是完整受害者数量。被清除的页面不一定全部包含敏感内容;识别出的 150 位客户不是上限;缓存清理也不能证明不存在私有副本。正确结论是:止血和恢复有不同的结束条件。
解析器补丁只能证明,已知路径在测试条件下不再产生新泄漏。缓存控制者必须另行删除存储片段。客户要判断哪些会话、令牌、API 密钥和密码可能越界,再在每一个依赖系统中轮换或撤销。完成证据不是生成了新字符串,而是旧权限已被拒绝。
最小共同边界,本地可逆选择
卢恒的“运行代码优先”给出了证明纪律。状态页写下“已解决”并不能结束事故;实际运行的边缘必须停止发出跨请求内存,功能状态必须可观察,旧凭证必须在曾经有效的地方失效,残留缓存则要由真正控制它们的各方查询和清理。
“最小初始规范”进一步收紧架构。多租户边缘的共同约束应当短而绝对:一个响应只能包含对该响应获得授权的数据。可选转换、解析器实现、上线节奏和功能关闭都留作本地选择。它们可以演进,也可以被拒绝,而不削弱隔离底线,也不要求所有客户共同承担同一种功能风险。
Cloudbleed 并没有证明共享基础设施不可行。它证明,共享必须有坚硬的膜,而每一条穿过这层膜的可选路径,都要能在整个服务消失之前先独立消失。
来源
会员简报
档案背景详情
使用相应会员等级登录,即可解锁完整简报与来源注释。
仅限 Strategic Circle
Strategic Circle
所有读者均可浏览。加入并登录后可解锁档案简报。
加入 Strategic Circle仅限 Leadership Alliance
Leadership Alliance
符合条件的 IP 资产所有者和管理层可登录查看 Leadership Alliance 简报。
加入 Leadership Alliance