仕組み: 新手の HTTP/2「Rapid Reset」DDoS 攻撃
Google Cloud Japan Team
※この投稿は米国時間 2023 年 10 月 11 日に、Google Cloud blog に投稿されたものの抄訳です。
多くの Google サービスおよび Cloud のお客様が、新手の HTTP/2 ベースの DDoS 攻撃のターゲットとされており、その数は 8 月にピークに達しました。この種の攻撃は、これまで報告されたどのレイヤ 7 攻撃よりもはるかに大規模なもので、最大規模の攻撃では 1 秒あたりに 3 億 9,800 万回のリクエストが送信されました。
この攻撃のほとんどは、Google のグローバル ロード バランシング インフラストラクチャによりネットワークのエッジで阻止され、サービスの停止には至りませんでした。影響は軽微であるものの、Google の DDoS 対応チームはこの攻撃を調査し、類似の攻撃をさらに軽減するために追加の防御策を実装しました。また Google の内部での対応とは別に、エコシステム全体で新手の HTTP/2 ベクトルに対応することを目的として、業界パートナーとの連携のもとでの開示プロセスを主導することにも関わってきました。
以下では、この数年でレイヤ 7 攻撃でよく使用されている手法、攻撃がこれほど大規模となった要因でもあるこの新しい攻撃の特異な点、この種の攻撃に対して効果的であると考えられる軽減戦略について説明します。この記事は、リバース プロキシ アーキテクチャの観点から執筆されています。このアーキテクチャでは、HTTP リクエストは、他のサービスにリクエストを転送するリバース プロキシによって終了されます。アプリケーション サーバーに統合される HTTP サーバーにも同じ考え方が適用されますが、考慮事項が若干異なるため、結果として異なる軽減戦略になる可能性があります。
DDoS に利用される HTTP/2 に関しての導入
2021 年後半以降、Cloud Armor で保護された Google のファーストパーティ サービスおよび Google Cloud プロジェクト全体で確認されたレイヤ 7 DDoS 攻撃の大半が、HTTP/2 に基づいたものでした。これは、攻撃数とピークのリクエスト レートの両方に関してです。
HTTP/2 は効率性を主な目標として設計されました。ただ残念なことに、正規のクライアントのために HTTP/2 をより効率的なものとするこの特徴が、DDoS 攻撃もより効率的なものとすることにつながっています。
ストリーム多重化
HTTP/2 では、エンドポイント間でさまざまメッセージ(フレーム)を送信するための双方向の抽象化である「ストリーム」が使用されます。「ストリーム多重化」は、HTTP/2 のコアとなる機能であり、各 TCP 接続の使用率の増加に寄与します。ストリームは接続の両端で追跡可能な形で多重化され、この際に 1 つのレイヤ 4 接続しか使用されません。ストリーム多重化のおかげでクライアントは、複数の個別の接続を管理することなく、複数のリクエストを送信できるようになります。
レイヤ 7 DoS 攻撃を仕掛ける際の大きな制約の一つに、同時トランスポート接続の数があります。各接続には負荷が伴い、ソケット レコードおよびバッファに費やされるオペレーティング システムのメモリ、TLS handshake に費やされる CPU 時間などがあります。また、各接続で一意の 4 タプル(接続の両端それぞれに対応する IP アドレスとポートのペア)が必要で、2 つの IP アドレス間の同時接続の数がこれにより制限されます。
HTTP/1.1 では、各リクエストが順番に処理されます。サーバーがリクエストを読み取り、これを処理して、レスポンスを作成します。これが完了して初めて、次のリクエストの読み取りと処理が開始されます。実際にはこれは、単一の接続を介して送信できるリクエストのレートが、1 往復あたりで 1 つのリクエストになるということを意味します。この 1 往復には、ネットワーク レイテンシ、プロキシでの処理時間、バックエンドでのリクエスト処理時間が含まれます。HTTP/1.1 のパイプラインは、接続のスループットを向上させる目的で、一部のクライアントおよびサーバーで利用できますが、正規のクライアントの中では普及していません。
HTTP/2 では、クライアントは複数の同時ストリームを単一の TCP 接続で開くことが可能で、それぞれのストリームが 1 つの HTTP リクエストに対応します。理論的には、同時に開くストリームの最大数はサーバーにより制御できます。ただし実際には、クライアントがリクエストごとに 100 のストリームを開くことは可能で、サーバーはこれらのリクエストを並列で処理します。ここで重要なのは、サーバーの上限は一方的に調整できないということです。
たとえば、クライアントは 100 のストリームを開いて、各ストリーム上で 1 往復のリクエストを送信できます。プロキシは各ストリームを順番に読み取って処理しますが、バックエンド サーバーに送信されるリクエストは再度並列化できます。クライアントは、前のストリームに対するレスポンスを受け取り次第、新しいストリームを開くことができます。この場合スループットが効率的で、単一の接続で、1 往復で 100 のリクエストを送信できます。また、ラウンドトリップ時間も HTTP/1.1 リクエストと同様です。通常これにより、それぞれの接続の使用率がほぼ 100 倍になります。
HTTP/2 Rapid Reset 攻撃
HTTP/2 プロトコルの場合、クライアントは RST_STREAM フレームを送信することで、前のストリームをキャンセルするようにサーバーに伝えることができます。このプロトコルでは、クライアントとサーバーが何かしらの形で連携してキャンセルを行う必要はなく、クライアントが一方的にキャンセルを実行できます。また、サーバーが RST_STREAM フレームを、その TCP 接続から来るそれ以外のデータが処理される前に受け取るのであれば、キャンセルがすぐに反映されるであろうと想定することもできます。
この攻撃が Rapid Reset と呼ばれている理由は、リクエスト フレームを送信した後即座にエンドポイントが RST_STREAM フレームを送信できる能力に依存しているからです。他のエンドポイントが動き始めた後即座に、リクエストがリセットされます。リクエストはキャンセルされますが、HTTP/2 接続は開かれたままです。
HTTP/1.1 および HTTP/2 のリクエストとレスポンスのパターン
この機能に基づいた HTTP/2 Rapid Reset 攻撃はシンプルなものです。標準の HTTP/2 攻撃と同様に、クライアントが大量の数のストリームを一度に開きます。ただこの際に、クライアントは各リクエスト ストリームに対するサーバーやプロキシからのレスポンスを待たずに、各リクエストを即座にキャンセルします。
ストリームを即座にリセットできるということは、各接続で送信されるリクエストの数を制限なく増やせることを意味します。攻撃者はリクエストを明示的にキャンセルするため、開いている同時ストリームの数の上限を超えることはありません。実行中のリクエストの数がラウンドトリップ時間(RTT)に依存しなくなり、利用可能なネットワーク帯域幅にのみ依存するようになります。
典型的な HTTP/2 サーバーの実装では、サーバーはキャンセルされたリクエストに対してもそれなりの量の作業を行う必要があり、これには、新しいストリーム データ構造の割り当て、クエリの解析およびヘッダーの解凍、URL のリソースへのマッピングなどがあります。リバース プロキシ実装の場合は、RST_STREAM フレームが処理される前に、リクエストがプロキシ経由でバックエンド サーバーに送信される可能性があります。一方で、リクエストの送信でクライアントにかかる負担はほとんどありません。そのため、サーバーとクライアントとの間で負担の非対称性が生じ、これが不正利用される要因になっています。
また、リクエストの作成後、それを明示的かつ即座にキャンセルする場合、リクエストに対するレスポンスがリバース プロキシ サーバーからまったく送信されなくなるということが、攻撃者にとっての利点の一つとなっています。レスポンスが作成される前にリクエストをキャンセルすることで、ダウンリンク(サーバー / プロキシから攻撃者)の帯域幅を減らすことができます。
HTTP/2 Rapid Reset 攻撃のバリアント
最初の DDoS 攻撃以降の数週間で、Google は Rapid Reset 攻撃のバリアントをいくつか確認しました。これらのバリアントは一般的には、最初のバージョンほど効果的なものではありません。ただそれでも、標準の HTTP/2 DDoS 攻撃よりは効果的である可能性があります。
1 つ目のバリアントでは、ストリームが即座にキャンセルされるわけではありません。大量のストリームを一度に開いて、ある程度の時間待機してからそのストリームをキャンセルして、その後即座に別のストリームを大量に開きます。この攻撃は、受信 RST_STREAM フレームのレートのみに基づいた軽減策(たとえば、接続を閉じるまでにその接続で 1 秒あたりで 100 の RST_STREAM までを許可するなど)を迂回する可能性があります。
この攻撃は接続の使用率を最大化せず、キャンセルによる攻撃という大きな利点はありませんが、それでも、標準の HTTP/2 DDoS 攻撃に勝る効率性があります。そして、このバリアントに関しては、レートを制限するストリームのキャンセルに基づいた軽減策の場合、かなり厳しい制限を設定しなければ効果がありません。
2 つ目のバリアントは、ストリームのキャンセルをまったく行いません。その代わりに、サーバーがアドバタイズする数よりも多くの同時ストリームを開いて増やすことを楽観的に試みます。この手法が標準の HTTP/2 DDoS 攻撃に勝る点は、クライアントがリクエスト パイプラインを常に満たした状態にすることが可能な点であり、ボトルネックとしてのクライアント プロキシ RTT を排除できます。また、HTTP/2 サーバーが即座に応答するリソースにリクエストが向けられている場合、ボトルネックとしてのプロキシ サーバー RTT が排除されます。
現時点の HTTP/2 RFC である RFC 9113 では、大量のストリームを開く試行で、接続全体ではなく、上限を超えたストリームのみを無効にするように提案しています。ほとんどの HTTP/2 サーバーはこのようなストリームを処理しません。前のストリームに対応した後、ほとんど即座に新しいストリームを受け入れて処理するということが、キャンセルを実行しない攻撃バリアントを実現可能なものとする要因になっていると Google は考えています。
軽減のための多面的アプローチ
Google は、個々のリクエストを単にブロックすることが、この種の攻撃に対抗するための現実的な軽減策であるとは考えてはいません。そうではなく、不正が検出された際には TCP 接続全体を閉じる必要があります。HTTP/2 には、接続を閉じるための組み込みのサポートが備わっており、GOAWAY フレームタイプを使用してこれを実行できます。RFC では、正常に接続を閉じるためのプロセスが定義されています。これによれば、まず新しいストリームを開くことについて制限を加えない GOAWAY の情報を送信して、1 往復が完了してから、追加のストリームを開くことを禁止する別の GOAWAY を送信します。
ただし、このグレースフル GOAWAY プロセスは通常、悪意のあるクライアントに力強く対抗できるような形では実装されていません。このような軽減策の場合、接続は Rapid Reset 攻撃に対して長時間にわたり脆弱なままになります。また、受信リクエストを止めるわけではないため、軽減策の構築のために使用されるべきではありません。そうではなく、GOAWAY はストリームの作成を即座に制限する目的で設定されるべきです。
ここで、どの接続が不正使用されているかを判断するという問題が生じます。リクエストをキャンセルする HTTP/2 プロトコルの機能はリクエストの処理を管理しやすくするためのものであり、リクエストをキャンセルするクライアントが本質的に不正であるわけではありません。ユーザーがそのページから離れたため、あるいは、アプリケーションがロング ポーリングのアプローチを使用していて、クライアントサイドのタイムアウトが発生したため、ブラウザがリクエストしたリソースが必要なくなったということは、通常よくあることです。
このような攻撃ベクトルに対する軽減策としてはさまざまなものが考えられます。ただ、その中心となるものは、接続の統計情報を追跡して、さまざまなシグナル、ビジネス ロジックを使用して、各接続の有用性を判断するということです。たとえば、ある接続でリクエスト数が 100 を超えていて、そのうちの 50% 以上がキャンセルされている場合、この接続は軽減のための対応の対象候補となるでしょう。対応の程度や種類に関しては、それぞれのプラットフォームに対するリスクによりますが、対応策として考えられるものには、先ほど説明した GOAWAY フレームの強制や、TCP 接続を即座に閉じることなどがあります。
キャンセルを実行しないこの攻撃バリアントに対抗するための軽減を実行するために、同時ストリームの上限を超過した接続を HTTP/2 サーバーが閉じるようにすることを推奨します。これは即座にすることも、少ない回数の繰り返しの攻撃が発生した後にすることもできます。
他のプロトコルへの適用可能性
プロトコルが異なるため、この攻撃手法が HTTP/3(QUIC)でもそのまま使われるとは考えていません。また、Google は、現時点で DDoS 攻撃ベクトルとして HTTP/3 を大規模に使用した事例を確認していません。とは言うものの、HTTP/3 サーバー実装においても、これまで説明した HTTP/2 での軽減策と同様に、単一のトランスポート接続により行われる作業の量を制限するメカニズムを、先を見越して導入することを推奨します。
業界における連携
Google の DDoS 対応チームによる調査の初期段階において、また、業界パートナーとの連携を通じて、この新手の攻撃手法が、サービスで HTTP/2 プロトコルを提供しているあらゆるエンティティに幅広く影響する可能性を持っていることが明らかとされています。Google は、過去に多くのさまざまな取り組みで使用されてきた、すでに存在する調整された脆弱性開示グループを利用して、連携のもとでの脆弱性開示プロセスの主導に関わってきました。
この開示プロセスでチームは、インフラストラクチャ企業やサーバー ソフトウェア プロバイダなどの HTTP/2 の大規模実装者に情報を伝えることを重視しました。これらの事前通知の目的は、調整されたリリースに向けた軽減策の開発、準備を行うことでした。このアプローチではこれまで、サービス プロバイダを対象として広範囲にわたる保護を実現したり、ソフトウェア アップデートの形で多くのパッケージ、ソリューションを対象として保護したりしてきました。
連携のもとでの開示プロセスにて、Google はさまざまな HTTP/2 実装に対する修正を追跡する目的で、CVE-2023-44487 を予約しました。
次のステップ
今回の投稿で説明した新手の攻撃は、あらゆる規模のサービスに多大な影響を及ぼす可能性があります。HTTP/2 サービスを持つすべてのプロバイダは、この問題の影響が及ぶかに関して評価する必要があります。一般的なウェブサーバー、プログラミング言語向けのソフトウェア パッチやアップデートが、現時点または近い将来に適用可能になる可能性があります。可能な限り早めにこのような修正を適用することを推奨します。
Google のお客様には、ソフトウェアへのパッチ適用や、アプリケーション ロードバランサおよび Google や既存の Google Cloud アプリケーション ロード バランシング ユーザーの保護を担っている Google Cloud Armor の有効化を推奨します。
-スタッフ ソフトウェア エンジニア Juho Snellman
-スタッフサイト信頼性エンジニア Daniele Iamartino