系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

一条 WAF 正则因灾难性回溯打满全球边缘 CPU,Cloudflare 全网 502 持续 27 分钟。 本文是公开报告的摘要与解读,事实以原始报告为准

本文是公开报告的摘要与解读,事实以原始报告为准。

事件概要

2019 年 7 月 2 日,Cloudflare 发生全球性故障。13:31 UTC,一名工程师合并了包含 XSS 检测规则变更的已批准 pull request;13:37,TeamCity 完成规则构建和测试,没有发现 CPU 问题;13:42,自动化全球部署开始,故障随之出现。

问题规则包含一段会“灾难性回溯”的正则表达式,导致处理 HTTP/HTTPS 流量的 CPU 核心几乎达到 100% 使用率。边缘节点无法及时处理请求,访问任意 Cloudflare 域名的用户看到 502 错误页,Cloudflare Dashboard 和 API 也因为流量经过边缘而不可用。

报告给出的核心故障时长是 27 分钟。14:07,工程师执行“global terminate”全局终止 WAF;14:09,全球流量和 CPU 回到预期水平;14:52,WAF 在全球重新启用。

时间线

时间(UTC) 事件
13:31 工程师合并包含 XSS WAF 规则变更的已批准 pull request
13:37 TeamCity 构建规则并运行测试,测试通过,未发现 CPU 问题
13:42 自动化全球部署开始,生成变更请求工单;27 分钟故障开始
13:45 第一个 PagerDuty 告警:外部合成 WAF 测试失败;随后其他端到端测试、全球流量下降告警、大量 502 和 PoP CPU 耗尽报告出现
13:45–14:00 SRE 宣布 P0 并升级;伦敦工程团队中断内部演讲;最初怀疑是新型攻击;有负责人报告失去 80% 流量
14:00 确认 WAF 是原因,排除攻击;实时 CPU 数据、strace 和错误日志指向 WAF
14:02 工程师提出使用“global terminate”;Cloudflare Access 故障导致控制台认证受阻,Jira 和构建系统也不可达,绕过流程不熟,部分凭据过期
14:07 执行全局 WAF 终止
14:09 全球流量和 CPU 回到预期水平,核心故障结束;其他防护机制仍在运行
14:09–14:52 WAF 保持关闭;在一个城市用部分流量做正反测试,并先移除该城市的付费客户流量
14:52 团队确认已理解原因且修复到位,WAF 全球恢复

影响面

故障是全球性的。报告称问题规则运行在“全球 Cloudflare 网络中每一个处理 HTTP/HTTPS 流量的 CPU 核心”上,CPU 使用率接近 100%。用户访问任意 Cloudflare 域名都会看到 502 错误页,Cloudflare Dashboard 和 API 也不可用,因为它们同样经过 Cloudflare 边缘。

报告引述一位 Solutions Engineering 负责人对作者说,当时“失去了 80% 的流量”。这不是报告给出的精确统计,而是事故现场的业务判断;报告本身没有给出更细的请求比例或客户数量。

14:07 全局终止 WAF 后,核心流量在 14:09 恢复;WAF 直到 14:52 才重新启用。报告明确提到,WAF 关闭期间其他防护机制仍在运行。

根因

触发点是 WAF Managed Rules 中的一条 XSS 检测规则。它走完了正常的 pull request、审批、构建和测试流程,但 WAF 规则变更绕过了 Cloudflare 常规的 DOG/PIG/Canary 分阶段发布。规则原本要以“simulate”模式部署,但报告指出:即使是 simulate 模式,规则也必须对真实流量执行,只是不拦截。

规则中的关键片段是 .*(?:.*=.*),报告为便于分析将其简化为 .*.*=.*。两个连续的贪婪 .* 可以用很多种方式划分同一段输入;当后面的字面量匹配失败时,PCRE 会回溯尝试这些可能性。报告给出的测量包括:x=x 需要 23 步;x= 加 20 个 x 需要 555 步;20 个 x 且没有 = 需要 4,067 步;再加一个分号后升到 5,353 步。

Lua WAF 使用的 PCRE 没有防止表达式失控的机制;更糟的是,一个本可限制 CPU 使用的保护在数周前的 WAF 重构中被误删。规则通过 Quicksilver 在几秒内分发到全球,报告给出的 p99 是 2.29 秒,因此故障几乎瞬间覆盖全网。

报告把这次事故描述为“多个原因的不太可能的汇聚”,而不是单一 bug。测试只验证规则是否匹配,不测量 CPU 消耗,测试套件运行时间也没有明显增加;SOP 允许非紧急规则不做分阶段发布就全球上线;回滚需要两次完整的 WAF 构建,速度太慢;全球流量下降告警触发偏晚;状态页更新偏慢;Cloudflare Access 故障又让控制台认证和绕过流程变得困难。

处置与恢复

13:45 的第一个合成测试失败后,告警迅速扩展为全球 502 和 CPU 耗尽报告。SRE 在 14:00 前宣布 P0,伦敦工程团队中断内部演讲加入处置。最初怀疑是新型攻击,14:00 通过实时 CPU、strace 和错误日志确认问题来自 WAF,排除了攻击。

14:02 提出“global terminate”,但 Cloudflare Access 故障导致控制台认证不可用,Jira 和构建系统也不可达,绕过流程不熟悉,部分凭据已过期。14:07 终止操作执行,14:09 流量和 CPU 恢复。之后 WAF 保持关闭,团队在一个城市用部分流量做负向和正向测试,并先移除该城市的付费客户流量;14:52 确认理解原因且修复到位后,WAF 全球恢复。

报告列出的改进包括:重新引入被误删的 CPU 使用保护(已完成);人工检查 WAF Managed Rules 中全部 3,868 条规则(已完成);为所有规则增加性能分析(预计 7 月 19 日);切换到 RE2 或 Rust 正则引擎(预计 7 月 31 日);修改 SOP,实施分阶段发布,同时保留紧急全球部署能力;提供把 Dashboard 和 API 移出 Cloudflare 边缘的应急方式;自动化状态页更新;长期把 WAF 迁移到新的防火墙引擎。

教训

第一,正则表达式是生产风险,不只是代码细节。一个短表达式就能让全球边缘 CPU 饱和;用户可控输入的正则必须使用线性时间引擎、设置超时或限制回溯。

第二,测试要测量性能,而不只是匹配结果。规则匹配正确但 CPU 爆炸,传统功能测试发现不了;性能基线、最坏输入和超时测试应进入发布门禁。

第三,配置变更要像代码一样灰度。WAF 规则绕过 DOG/PIG/Canary,直接全球部署,是事故能瞬间扩散的关键;非紧急变更应先小流量验证。

第四,kill switch 必须快、可用、练过。事故中控制台认证依赖 Cloudflare Access,而 Access 也故障;绕过流程不熟、凭据过期,都会拉长恢复时间。

第五,状态页和告警要独立且自动化。全球流量下降告警触发偏晚、状态页更新偏慢,会放大外部感知的故障时长。

第六,删除保护要留下记录。CPU 保护在重构中被误删,说明安全机制也需要测试和审计,不能默默消失。

对本站读者的检查清单

恢复手册与同类复盘

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么一条只用于 XSS 检测的 WAF 规则,会让全球边缘返回 502?
  2. .*(?:.*=.*) 为什么会发生灾难性回溯?报告给了哪些测量?
  3. 从发布流程和应急控制角度,这次事故留下了哪些改进项?
答案
  1. 规则对真实流量执行,其中的正则发生灾难性回溯,耗尽处理 HTTP/HTTPS 的 CPU;边缘无法及时处理请求,于是返回 502,Dashboard 和 API 因经过边缘也一起不可用。
  2. 两个贪婪 .* 可以用多种方式划分同一段输入,后续字面量匹配失败时 PCRE 会回溯尝试;报告测量了 x=x 23 步、20 个 x 无等号 4,067 步、带分号 5,353 步等情况。
  3. 为规则增加性能分析和 CPU 保护;改用 RE2 或 Rust 正则;WAF 变更实施分阶段发布;提供独立的 kill switch 和 Dashboard/API 应急入口;自动化状态页更新。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令