Cloudflare世界障害(2019)— 1本の正規表現
たった1本の正規表現が全世界を27分止めた事故から、バックトラッキング爆発の原理、即時グローバル展開の危うさ、届く緊急停止の設計までを一気に学べる。
- 2019年7月2日13:42 UTC、WAF新ルールの正規表現が壊滅的バックトラッキングを誘発。全エッジのCPUが100%に張り付き、約27分間世界中で502が返り、トラフィックは約80%減少した。
- 核心は.*.*=.*。バックトラック型は2つの.*の分割を総当たりする。実測はx=xの23手順に対し、不一致を強いたx20個の入力では5,353手順へ増えた。
- WAFルールは段階展開を迂回し数秒で全世界へ配信され、CPU暴走の保護も直前の改修で消えていた。教訓は線形時間保証エンジン(RE2/Rust regex)・性能テスト・例外なき段階展開・届く緊急停止。
何が起きたか(日時・影響範囲)
2019年7月2日 13:42 UTC、CloudflareがXSS検知を改善するWAFマネージドルールを展開した直後、世界中の全エッジサーバーでHTTP/HTTPSを処理するCPUが100%に張り付きました。新ルールに含まれた1本の正規表現が壊滅的バックトラッキング(catastrophic backtracking)を起こしたためです。Cloudflareを経由するサイトは約27分間502 Bad Gatewayを返し、グローバルのトラフィックは約80%落ち込みました。規模と同時性から当初はDDoS攻撃も疑われましたが、外部要因はなく、自社のダッシュボード・API・社内認証まで巻き込んだ自滅型の障害でした。
タイムライン
公式ポストモーテムが公表した時系列です(すべてUTC)。
| 時刻(UTC) | 出来事 |
|---|---|
| 13:31 | 問題の正規表現を含む変更がマージされる |
| 13:37 | CIがルールをビルドしテストを実行、全件パス(CPU消費は未測定) |
| 13:42 | 新ルールの自動展開が開始。数秒で全エッジへ伝播し、直後からCPUが急騰 |
| 13:45 | 最初のPagerDutyアラート(WAFの合成テスト失敗)。以後、警報が連鎖 |
| 14:00 | 原因コンポーネントをWAFと特定し、攻撃の可能性を棄却 |
| 14:02 | WAFのグローバル停止(global kill)を決断 |
| 14:07 | 内部システムへの認証に難航しつつ、停止を実行 |
| 14:09 | トラフィックとCPUが世界全域で正常化 |
| 14:52 | 修正の検証を経て、WAFを全世界で再有効化 |
決断(14:02)から実行(14:07)までの5分は偶然の遅れではありません。Cloudflareは自社製品を自社で使っており、認証サービスのAccessが障害に巻き込まれて内部管理パネルへログインできず、さらに一部のメンバーは「管理パネルを長期間使わないと資格情報を無効化する」セキュリティ機能でアクセス権を失っていました。緊急停止は「存在」していても、「届く」までに時間を要したのです。
根本原因の技術解説
問題の正規表現は次のとおりです。
(?:(?:\"|'|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*)))
致命部は末尾の .*(?:.*=.*)、本質だけ残せば .*.*=.* です。CloudflareのWAFはNGINX上のLuaで実装され、正規表現は内部でPCREにより評価されていました。PCREはNFAを深さ優先のバックトラッキングで模倣するため、.* が2つ並ぶと「1つ目がどこまで飲み込み、2つ目がどこから引き継ぐか」という分割点の候補が入力長のぶん生まれ、後続の = の照合に失敗するたびに分割点を巻き戻して再試行します。付録の実測では、x=x のマッチに23ステップ、x= の後ろに x を20個並べると555ステップ。さらに末尾に ; を要求する .*.*=.*; は、どう分割しても失敗が確定するまで全候補を試し尽くすため、x=x で90ステップ、x が20個で5,353ステップへ爆発します。試行回数は入力長に対しおおむね二乗で増え、量指定子が入れ子なら指数にもなり得ます。WAFは全リクエストのURIやヘッダへルールを適用するので、攻撃どころか通常トラフィックだけでCPU時間が枯渇し、プロキシ本体が応答不能となって502を返しました。
細工された入力で正規表現の実行時間を爆発させる攻撃はReDoS(Regular expression DoS)と呼ばれます。本件は攻撃ではなく自前のパターンが引き金でしたが、「バックトラッキング型エンジン+曖昧な量指定子+任意入力」という成立条件は同一です。
なぜ防げなかったか
横にスクロール
Cloudflareの通常のソフトウェアリリースは、社員だけが使うDOG PoP、ごく一部の顧客トラフィックを流すPIG PoP、カナリアPoP群を経て全世界へ、という段階的ロールアウトを踏みます。しかしWAFマネージドルールは「新種の攻撃に数分で対処する」ための例外としてこの経路を完全に迂回し、分散KVストアQuicksilverで即時に全世界へ一斉配信されていました(配布のp99は2.29秒)。カナリアで異常を観測する機会は、構造的に存在しなかったのです。
さらに悪条件が重なります。CIのテストはルールの検知精度だけを確認し、CPU消費を測定していませんでした。正規表現の暴走を打ち切る保護機構は、数週間前のリファクタリングで誤って取り除かれていました(皮肉にも、WAFのCPU使用量削減が目的の改修でした)。新ルールはブロックしない「シミュレート」モードでの展開でしたが、判定には正規表現の実行そのものが必要なため、実行コストが欠陥である本件では防波堤になりませんでした。
ログのみのモードでもルールは実トラフィックに対して実行されます。誤検知の確認には有効でも、評価コストの爆発はブロック設定と同じ被害を生みます。
教訓(原則として一般化)
| 観点 | バックトラッキング型(PCRE等) | 線形時間保証型(RE2 / Rust regex) |
|---|---|---|
| 最悪計算量 | 入力長に対し二乗〜指数 | 入力長に対し線形を保証 |
| 後方参照など | 利用できる | 保証と引き換えに非対応 |
| 暴走リスク | パターン次第で常に残る | 原理的に排除 |
| 必要な運用対策 | 実行ステップ・時間の上限設定が必須 | エンジン選択自体が対策 |
Cloudflareの再発防止策は、外れていたCPU保護の復活、全3,868本のマネージドルールの点検、テストスイートへの性能プロファイリング追加、そしてRE2またはRustのregexという実行時間を保証するエンジンへの移行でした。線形時間エンジンはThompson構成のNFAの状態集合を入力1文字ごとに同時に進めるため、後方参照などの機能を捨てる代わりに、どんなパターンでも実行時間が入力長に比例します。
- 任意入力に触れる正規表現には線形時間保証か実行上限を課す: エンジンを替えられないなら、PCREのマッチ上限のような打ち切りを必ず設定します。
- 設定もコードと同じ危険物として段階展開する: 数秒で全世界へ届く配信路は、最速で全世界を壊す経路でもあります。緊急用の即時展開は残しつつ、既定はカナリア観測を挟みます。
- 緊急停止は「届くこと」まで設計する: 障害下でも認証・経路・権限が生きているかを訓練で確かめます。Cloudflareもダッシュボード・APIを自社エッジから切り離す緊急手段を整備しました。
カナリアリリースなど段階展開の設計はDevOps、WAFや多層防御の考え方はセキュリティの各トピックも参照してください。
障害事例の記事ガイド
Cloudflare世界障害(2019)— 1本の正規表現を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
Cloudflare
比較で見る軸
難易度: advanced / カテゴリ: 障害事例 / タグ数: 6
導入後に効く点
核心は.*.*=.*。バックトラック型は2つの.*の分割を総当たりする。実測はx=xの23手順に対し、不一致を強いたx20個の入力では5,353手順へ増えた。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 障害事例
- タグ数
- 6
判断チェックリスト
- 自社の用途が「Cloudflare / 正規表現」に近いか確認する。
- 強みである「2019年7月2日13:42 UTC、WAF新ルールの正規表現が壊滅的バックトラッキングを誘発。全エッジのCPUが100%に張り付き、約27分間世界中で502が返り、トラフィックは約80%減少した。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。