新しい HTTP/2「Rapid Reset」DDoS 攻撃
(cloud.google.com)- Google のサービスと Google Cloud の顧客を狙った HTTP/2 ベースの Layer 7 DDoS が 2023年8月にピークに達し、最大で 毎秒3億9,800万リクエスト を超えた
- 攻撃の大半は Google の グローバル ロード バランシング インフラ で遮断され、障害には至らず、その後、類似攻撃を緩和するための保護策が追加された
- Rapid Reset は、リクエスト直後に RST_STREAM フレーム でストリームだけをキャンセルし、接続は維持することで、同時ストリーム数の制限を回避しながらリクエスト生成を繰り返す
- サーバーは、キャンセルされたリクエストに対してもヘッダー圧縮解除、URL マッピング、バックエンド プロキシなどの処理を実行しうるため、コストの非対称性 が大きくなる
- HTTP/2 運用者は影響有無を確認してパッチを適用する必要があり、悪用を検知した場合は個別リクエストの遮断よりも TCP 接続自体の終了 が重要な緩和策となる
攻撃規模と Google の対応
- Google のサービスと Cloud 顧客は、2023年8月にピークを迎えた HTTP/2 ベースの Layer 7 DDoS 攻撃 の標的となった
- 最大の攻撃は 毎秒3億9,800万リクエスト を超え、これまで報告されていた Layer 7 攻撃をはるかに上回る規模だった
- Google の グローバル ロード バランシング インフラ がネットワーク エッジで攻撃の大半を遮断した
- 障害は発生しなかった
- 影響は限定的な水準にとどまった
- Google DDoS Response Team は攻撃を検証した後、類似攻撃を減らすための追加保護策を適用した
- Google は業界パートナーとともに、新しい HTTP/2 攻撃ベクトルに対応するための 協調開示プロセス を主導した
HTTP/2 が DDoS に有利になる点
- 2021年末以降、Google の 1st-party サービスと Cloud Armor によって保護されている Google Cloud プロジェクトで観測された Layer 7 DDoS 攻撃の多くは HTTP/2 ベースだった
- 攻撃件数の面でも多かった
- 最大リクエスト率の面でも高かった
- HTTP/2 の効率性は、正当なクライアントだけでなく DDoS 攻撃の効率も高めうる
-
ストリーム マルチプレキシング
- HTTP/2 は、双方向の抽象化である ストリーム(stream) によってエンドポイント間でフレームを転送する
- ストリーム マルチプレキシング により、1本の TCP 接続で複数のリクエストを同時に処理できる
- Layer 7 DoS 攻撃の主な制約は同時伝送接続数である
- 各接続は、ソケット レコードとバッファのために OS メモリを使用する
- TLS ハンドシェイクには CPU 時間が必要である
- 両端の IP アドレスとポートの組からなる一意の 4 タプルが必要である
- HTTP/1.1 は通常リクエストを直列処理するため、単一接続のリクエスト率は往復時間あたり 1 リクエストに近い
- HTTP/2 では 1本の TCP 接続上で複数の同時ストリームを開くことができ、各ストリームは 1つの HTTP リクエストに対応する
- 実環境ではクライアントがリクエストごとに 100 ストリームを開き、サーバーが並列処理できるため、単一接続の有効スループットは往復ごとに 100 リクエスト規模まで高められる
- その結果、接続利用率は HTTP/1.1 よりほぼ 100倍 高くなりうる
Rapid Reset の動作方式
- HTTP/2 では、クライアントは RST_STREAM フレーム を送って、以前のストリームのキャンセルをサーバーに通知できる
- キャンセルにクライアントとサーバーの個別の協調は不要である
- クライアントは一方的にキャンセルできる
- RST_STREAM フレームを受信したサーバーは、同じ TCP 接続上の他データより先にキャンセルを適用すると想定できる
- Rapid Reset は、リクエスト フレーム送信直後に RST_STREAM フレームを送る方式に依存する
- 相手エンドポイントに処理を開始させた後、素早くリクエストをリセットする
- リクエストはキャンセルされるが HTTP/2 接続は開いたまま維持される
- 攻撃手順は単純である
- 標準的な HTTP/2 攻撃のように一度に多数のストリームを開く
- サーバーやプロキシの応答を待たない
- 各リクエストを即座にキャンセルする
- 即時リセットが可能であれば、各接続は事実上無期限に多数のリクエストを進行中状態にできる
- 攻撃者は同時オープン ストリーム制限を明示的には超えない
- 進行中リクエスト数は往復時間(RTT)よりも利用可能なネットワーク帯域幅に左右される
- 一般的な HTTP/2 サーバー実装では、キャンセル済みリクエストに対しても処理が必要になる場合がある
- 新しいストリーム データ構造の割り当て
- クエリ解析
- ヘッダー圧縮解除
- URL をリソースにマッピング
- リバース プロキシ実装では、RST_STREAM フレームが処理される前にリクエストがバックエンド サーバーへプロキシされる場合がある
- クライアントはリクエスト送信コストをほとんど負担しないため、サーバーとクライアントの間に 悪用可能なコストの非対称性 が生じる
- リクエストがレスポンス作成前にキャンセルされると、リバース プロキシ サーバーはレスポンスを送らないため、サーバーやプロキシから攻撃者方向へ出ていく ダウンリンク帯域幅 も減少する
観測された亜種攻撃
- 初期 DDoS の後、数週間にわたって Rapid Reset の亜種が観測された
- これらの亜種は初期方式ほど効率的ではないが、標準的な HTTP/2 DDoS 攻撃よりは依然として効率的である可能性がある
-
バッチ キャンセル亜種
- 最初の亜種はストリームを即時にキャンセルせず、ストリームのまとまりを開いて少し待ってから一括でキャンセルする
- キャンセル直後に新たな大規模ストリーム群を再び開き、攻撃を継続する
- この方式は、受信 RST_STREAM フレーム比率だけを基準にする緩和策を回避できる
- 例: 接続ごとに毎秒最大 100 個の RST_STREAM のみ許可し、超過時に閉じるポリシー
- 接続利用率を最大化できないため、キャンセル攻撃の主な利点は小さくなる
- それでも標準的な HTTP/2 DDoS 攻撃より実装上効率的である可能性があり、ストリーム キャンセル比率制限だけではかなり厳しい制限が必要になる
-
非キャンセル亜種
- 2番目の亜種はストリームのキャンセルをまったく行わない
- その代わり、サーバーが広告した同時ストリーム数より多くのストリームを楽観的に開こうと試みる
- リクエスト パイプラインを継続的に満たせるため、クライアント-プロキシ間 RTT のボトルネックを減らせる
- HTTP/2 サーバーが即時に応答するリソースを対象にすれば、プロキシ-サーバー間 RTT のボトルネックも解消できる
- 現行の HTTP/2 RFC である RFC 9113 は、多すぎるストリームを開こうとする試みに対して、接続全体ではなく制限を超えたストリームだけを無効化すべきだと提案している
- 大半の HTTP/2 サーバーが超過ストリームを処理しない構造では、以前のストリーム応答直後に新しいストリームをほぼ即時に受け入れて処理できるため、非キャンセル亜種が可能になる
緩和戦略
- この攻撃ファミリーは、個別リクエストの遮断だけでは実用的な緩和策になりにくい
- 悪用が検知されたら、TCP 接続全体 を閉じる必要がある
- HTTP/2 は GOAWAY フレーム タイプで接続終了をサポートしている
- RFC は、まず新規ストリーム オープン制限を設定しない情報的 GOAWAY を送り、1 往復後に追加ストリーム オープンを禁止する GOAWAY を送る段階的終了手順を定義している
- しかし、このような段階的 GOAWAY 手順は悪意あるクライアントに対して十分に堅牢でない場合が多い
- Rapid Reset 攻撃に対して接続が長時間さらされすぎる
- 受信リクエストを止められない
- 緩和目的であれば、GOAWAY は ストリーム生成を即時に制限 するよう設定すべきである
-
悪用接続の判定
- クライアントがリクエストをキャンセルする行為自体が常に悪用とは限らない
- HTTP/2 のキャンセル機能は、リクエスト処理をより適切に管理するための機能である
- ユーザーがページを離れ、ブラウザーがもはや要求したリソースを必要としない場合
- クライアント側タイムアウトを持つ long polling 方式を使うアプリケーション
- 緩和は、接続統計を追跡し、複数のシグナルとビジネス ロジックで各接続の有用性を判断することに重点が置かれる
- 例えば、1 接続に 100 件を超えるリクエストがあり、そのうち 50% 以上がキャンセルされていれば、緩和対象候補になりうる
- 対応の規模と種類はプラットフォームごとのリスクによって異なる
- 強制的な GOAWAY フレーム
- TCP 接続の即時終了
- 非キャンセル亜種の緩和のため、HTTP/2 サーバーでは同時ストリーム制限を超えた接続を閉じる方式が推奨される
- 即時に閉じることもできる
- あるいは少数回の繰り返し違反の後に閉じることもできる
HTTP/3 への適用可能性
- Google は、プロトコル差異のため、この攻撃方式が HTTP/3(QUIC) にそのまま適用されるとは見ていない
- 現時点で Google は、HTTP/3 が大規模 DDoS 攻撃ベクトルとして使われている状況を確認していない
- それでも HTTP/3 サーバー実装には、単一の伝送接続が実行する作業量を制限するメカニズムを先行実装することが推奨される
- 議論された HTTP/2 緩和策と同様の方向性である
業界調整と CVE
- Google DDoS Response Team の調査初期から、この攻撃種別が HTTP/2 を提供するすべてのサービスに広く影響しうることが明らかになった
- Google は既存の協調的な脆弱性開示グループを活用して、協調的脆弱性開示プロセス を主導した
- 開示プロセスは、大規模な HTTP/2 実装者への通知に重点を置いた
- インフラ企業
- サーバー ソフトウェア提供者
- 事前通知の目的は、緩和策を開発し、協調開示スケジュールに合わせて準備することにあった
- 過去にも、このようなアプローチはサービス提供者による広範な保護適用や、複数のパッケージ・ソリューションでのソフトウェア更新提供につながってきた
- 協調開示プロセスの中で、複数の HTTP/2 実装の修正追跡のために CVE-2023-44487 が予約された
HTTP/2 サービス運用者がすべきこと
- この攻撃は、どの規模のサービスにも大きな影響を与えうる
- HTTP/2 サービスを提供するすべての事業者は、この問題への露出有無を評価すべきである
- 一般的な Web サーバーやプログラミング言語向けのソフトウェア パッチとアップデートは、現在または近い将来に提供される可能性がある
- 提供される修正は、可能な限り早く適用することが推奨される
- Google Cloud 顧客には、ソフトウェア パッチとともに Application Load Balancer、Google Cloud Armor の利用が推奨される
- Google Cloud Armor は、Google と既存の Google Cloud Application Load Balancing 利用者を保護してきた
1件のコメント
Hacker News のコメント
関連する進行中のスレッド:
これまでで最大の DDoS 攻撃、ピーク時に毎秒3億9,800万件超のリクエスト - https://news.ycombinator.com/item?id=37831062
HTTP/2 のゼロデイ脆弱性により記録的な DDoS 攻撃が発生 - https://news.ycombinator.com/item?id=37830998
HAProxy チームは、HTTP/2 のこの種の問題を2018年の時点ですでに見つけて緩和していたようで、好ましい: https://www.mail-archive.com/haproxy@formilux.org/msg44134.h...
https://www.nginx.com/blog/http-2-rapid-reset-attack-impacti...
一般的な HTTP/2 サーバー実装では、キャンセルされたリクエストに対しても、新しいストリームのデータ構造割り当て、クエリ解析、ヘッダーの展開、URL からリソースへのマッピングといった処理をかなり行う必要がある
リバースプロキシ実装では、
RST_STREAMフレームが処理される前にリクエストがバックエンドサーバーへプロキシされることもある。一方でクライアントはリクエスト送信にほとんどコストを払わないため、サーバーとクライアントの間に悪用可能なコストの非対称性が生じるHTTP/2 の設計時にこれを予想できなかったのは驚き。他のプロトコルでは増幅攻撃はすでによく知られていた。この攻撃がこれほど遅く明らかになったのも同じくらい驚きだが、最近まで HTTP/2 が十分広く配備されておらず、価値ある標的ではなかったのかもしれない
RST_STREAMは SPDY の初期バージョンにすでにあり、SPDY はおおむね2009年ごろに設計されたように見えるSlowloris のような攻撃もほぼ同時期に出てきたが、当時は広く知られていなかった。一方で SYN cookie は1996年に導入されているので、被害者が Y を払い攻撃者が X を払うタイプの攻撃には、歴史的な先例が明らかにある
ただ、Google を相手に本気で試みた点が珍しいところ
広告、トラッカー、肥大化したフロントエンドフレームワークをより速く配信するために HTTP/2 が必要だったわけだ。今では攻撃もより速く配信する
幸い HTTP/1.1 は今でも動作する。そのプロトコルが気に入らないなら、ブラウザ設定と Web サーバーでいつでも HTTP/1.1 を有効にできる
基盤プロトコルは小さく保つべきだという、もう一つの理由。HTTP/2 は SPDY まで含めれば10年以上存在していたのに、このタイプの攻撃が初めて表面化した
HTTP/3 と QUIC にはどんな驚きが隠れているのか気になる
「キャンセル」も「難しいコンピュータサイエンスの問題」のリストに入れるべき
そのリストの他の項目、たとえば off-by-one エラーやキャッシュ無効化のように、実際にはものすごく難しいわけではないが、過小評価され見落とされやすい。作成、コンストラクタ、初期化に使う時間の半分でも、破棄、クリーンアップ、解体、キャンセルの設計に使っていたなら、特にリソース枯渇バグはずっと減っていたと思う
await地点でFuture を即座にキャンセルし、呼び出しスタック全体も一緒にキャンセルできるので本当に良いGoogle が HTTP/2 を作った会社だという点を、みんなに思い出してほしい
今では自分たちが作った問題から我々を英雄的に救っているかのように語っているが、肝心の自分たちが作ったという部分は言わない。こうしたテック企業の厚かましさときたら。Microsoft も何十年もこんな調子だった
この攻撃で、普通のリクエスト洪水と違う新しい点が何なのか説明してもらえる?
HTTP/1.1 ではラウンドトリップ時間あたり1件のリクエストを送れた[0]。HTTP/2 の多重化ではラウンドトリップ時間あたり100件を送れる。この攻撃ではラウンドトリップ時間あたり事実上無限の数のリクエストを送れる。この記事の図が違いを示しているといいのだが、もしかすると上とは別の形の攻撃を指しているのかもしれない
[0] HTTP/1.1 パイプライニングを考慮すればラウンドトリップ時間の要素を1つ減らすことはできるが、実際のクライアントはほとんど HTTP/1.1 パイプライニングを使わないため、その利用自体が悪性トラフィックだという非常にはっきりしたシグナルになる
1つの接続内でリクエストとストリームのリセットを一緒に送ると、以前より接続/クライアントあたり多くのリクエストを送れるため、攻撃コストが安くなったり、ブロックが難しくなったりする可能性がある
ブログのヘッダーが何度も飛び出してきて、ページを読めなくしている