
HTTP/2からHTTP/1.1への変換を介してHTTPリクエストスムグリングの脆弱性を検出・悪用し、自動化されたヘッダースムグリング技術を用いてバックエンドの解析の不一致を特定します。
このツールは、フロントエンドサーバーによるHTTP/2 -> HTTP/1.1変換を介してHTTPリクエストスマグリングが達成可能なケースを検出し、悪用するのに役立ちます。
仕組みは以下の通りです:
攻撃者は、バックエンドサーバーによって2つの別々のリクエストとして認識されるようなリクエストを見つけたいと考えています。
フロントエンド<->バックエンド間のHTTP/1.1接続がkeep-aliveを使用している場合、フロントエンドは他のユーザーからのリクエストを同じ接続に送信する可能性があります。正当なリクエストの後に続く部分的なリクエストによって接続を「汚染」できれば、他のユーザーからのリクエストを取得できます。
その他のシナリオとしては、フロントエンドサーバーの保護や書き換えのバイパス、キャッシュポイズニング、キャッシュディセプションなどがあります。
HTTPリクエストスマグリングの詳細については、Portswigger Web Security Academy を参照してください。
HTTP/2では、すべてのHTTPヘッダーの名前と値はバイナリです。つまり、技術的には追加のスペースや改行を含むことができます。
RFC7540#10.3 では、HTTP/2リクエストをHTTP/1に変換する実装は、そのような変換から生じる文字集合の制限に注意する必要があると述べています。ほとんどの実装は実際にそれらを拒否します。それにもかかわらず、そのようなヘッダーを許可する実装が見つかることを期待しています。それらは変換されたHTTP/1.1リクエストをバックエンドに対して破壊します。
もう一つのポイントは、HTTPリクエストスマグリングに関連する最近の修正がHTTP/1.1パーサーにのみ実装されている可能性があることです。
一般的に、HTTP/2の実装の中には、HTTP/1.1におけるHTTPリクエストスマグリングに関する最近の研究をあまり認識しておらず、対応する緩和策を含んでいないものがあることを期待しています。
驚くべきことに、はい!
Cloudflareを通じてスペース文字を含むヘッダーをスマグルする可能性を見つけました。これにより、Cloudflare<->クライアント間のスマグリングへの扉が開かれます(Cloudflareクライアントのソフトウェアがヘッダー名を受け入れてトリミングする場合)。こちらのブログ記事をご覧ください。
まだ公開されていない別のバグ報奨金レポートもあります。これは、カスタムソフトウェアがHTTP/2ヘッダー内の改行をフィルタリングしないという事実を利用しており、スマグリングが100%発生します(他のユーザーのリクエストを見ることができます)。
しかし、このタイプの脆弱性は非常にまれであることは理解しています。HTTP/1.1とは異なり、HTTP/2の実装はそれほど多くなく、そのほとんどはセキュリティを考慮して作られているため、疑わしいまたは無効なヘッダーを拒否します。
このツールには、ターゲットがHTTPリクエストスマグリング攻撃に対して脆弱かどうかを自動的に検出しようとするサブコマンドがあります。この機能の背後にあるアルゴリズムは、このセクションで説明されます。
HTTPリクエストスマグリング攻撃を実行するには、まず1つのヘッダー(Content-Length または Transfer-Encoding)を実際に「スマグル」する必要があります。つまり、a) リクエストボディの終了位置を制御し、b) フロントエンドでは処理されないがバックエンドでは処理されるようなヘッダーを送信する必要があります。
これは通常、ヘッダーを何らかの方法で変更することで達成されます:名前の末尾にスペースやタブを追加する、値を半等価なものに置き換えるなど。
脆弱性検出アルゴリズムの基本的な考え方は、サーバーがスマグルされたヘッダーをあたかも Content-Length または Transfer-Encoding であるかのように実際に処理しているかどうかを検出することです。これは、有効な値と無効な値の両方のヘッダーを含む複数のリクエストを送信し、これら2つのグループからの応答を 区別 する方法があるかどうかを検出することで行います。
そのため、ツールの出力には「脆弱/非脆弱」という言葉は含まれません。単に、これら2つのグループのリクエストに対する応答を区別できるかどうかを示します。
ツールは、以下の2つの条件の少なくとも1つが満たされた場合、2つのHTTP応答セットを区別可能と見なします:
タイムアウトは、他のどの値とも等しくない一意のステータスコード値として扱われます。したがって、ツールは古典的な「タイミングによる検出」スキームよりも優先されます。
例を考えてみましょう。ダッシュをアンダースコアに置き換えることで transfer-encoding ヘッダーをスマグリングしようとしているとします。
transfer_encoding:zalupa を送信するたびにサーバーがステータス400で応答し、transfer_encoding:chunked のときにハングする(応答が返ってこない)場合、サーバーはおそらくそのヘッダーをトランスファーエンコーディングの値として処理していると言えます。理論的には、フロントエンドまたはバックエンドサーバーのどちらかである可能性があります。
最初のケースは興味深いものではありません。なぜなら、とにかく非スマグル版のヘッダーを送信できるからです。2番目のケースが私たちが探しているものです。すべてがHTTP/2上で行われるため、最初のケースはほとんどの場合回避可能です。HTTP/2サーバーは、リクエストヘッダーとは無関係に別の方法でリクエストボディの終了を決定し、HTTP/1.1のチャンク形式(16進数のチャンク長)でボディが来ることを期待しません。
検出技術の具体的なバリエーションは以下の通りです:
Transfer-Encoding: chunked のスマグル版(例:transfer_encoding:chunked)と、有効なボディ 0\r\n\r\n および無効なボディ 999\r\n を送信します。
応答が異なる場合、バックエンドサーバーがスマグルされたヘッダーを受信して処理していると確信できます。フロントエンドがこれを行う理由はありません。HTTP/2は送信したチャンク形式を使用しないため、無効になります。
無効なリクエストでは、バックエンドがより多くのデータが到着するのを待つため、ハングする(つまり、リクエストがタイムアウトする)と予想されます。
これは最も信頼性の高い検出方法です。ボディを読み取るときにサーバーがハングした場合、HTTP/2リクエストでHTTP/1.1のトランスファーエンコーディングが使用されていないため、何かがおかしいと言えます。
Transfer-Encoding のスマグル版と、有効なボディ 0\r\n\r\n および無効なボディ X\r\n\r\n を再度送信します。
上記と同じケースですが、ボディを読み取る代わりに、バックエンドが少なくともそれを検証することを期待します。
Content-Length ヘッダーのスマグル版を値 1 と -1 で送信します。
どちらの値もフロントエンドの観点からは無効です。HTTP/2ではボディ長を決定する別のメカニズムがあり、どちらの場合も実際のリクエストボディは送信されません。応答が異なる場合、ヘッダーを解析したのはバックエンドサーバーであると推定します。
この方法は最も信頼性が低いです。フロントエンドは、Content-Lengthが無効な値の場合と、実際の長さと一致しない場合で異なるエラーを発行する可能性があります。
有効/無効のリクエストの複数のペアを送信することで、ランダムな偽陽性の可能性を減らすことができます。一方で、「有効な」リクエストに対する応答と「無効な」リクエストに対する応答を分離する方法がまったくないとわかった場合、早期に停止することもできます。
このツールは、ヘッダーをさまざまな方法で変更することにより、複数のスマグリング技術を採用しようとします。どれも新しいものではなく、同時に明白でもありません。
ヘッダーをスマグルするために、ヘッダーにスペースを追加します。これは最も一般的で古典的な方法です。フロントエンドでは処理されず、バックエンドにそのまま送信され、バックエンドがスペースを取り除くことを期待します。
ツールは、 、\t、\v、\x00、およびUnicode文字など、さまざまな文字をスペースとして試します。
ヘッダーをスマグルするために、ダッシュ(-)をアンダースコア(_)に置き換えます。バックエンドが何らかの形でCGIにインスパイアされている場合、Header-Name のようなヘッダーを HEADER_NAME 形式に変換する可能性があります。その場合、ダッシュはとにかくアンダースコアになります。ボディの解析方法を決定する際、そのようなバックエンドはおそらくヘッダーディクショナリから CONTENT_LENGTH / TRANSFER_ENCODING の値を要求し、それがそこに存在することになります。
これはHTTP/2固有のものです。HTTP/2はバイナリプロトコルであるため、ヘッダー名または値に改行を送信しようとすることができます。標準はそれを禁止していますが、それでもこれを受け入れる実装が見つかることを期待しています。
HTTP/2 -> HTTP/1.1変換中に、ヘッダーは2つの異なるヘッダーに分割されます。つまり、リクエストはバックエンドにとって異なって見えます。
ヘッダーをスマグルするために、改行の後にヘッダーの名前と値を配置します。たとえば、名前が "Transfer-Encoding" で値が "chunked" のヘッダーは、名前が "fake" で値が "fake\r\ntransfer-encoding: chunked" のものになります。
バックエンドが高水準言語を使用し、十分なヘッダー検証を行っていない場合、大文字小文字の変換を何かを行う前に実行し、Unicode対応関数を使用する可能性があります。幸いなことに、TRANSFER-ENCODING には文字 S が含まれており、これは ſ (\u017f) を大文字にしたものです。
同様に、Transfer-Encoding の値を小文字に変換するバックエンドを探すこともできます。K の代わりに \u212a を使って chunKed の代わりに chunKed を送信します。
もちろん、フロントエンドがUTF-8のヘッダー名/値をバックエンドに渡す必要があります。
ツールをインストールするには、go install github.com/neex/http2smugl@latest を実行します。
このツールには、request と detect の2つのサブコマンドが含まれています。1つ目はHTTP/2リクエストを作成するためのものです。ほとんどのクライアントツールは無効なヘッダーを受け入れないため、ユーザー入力をそのままサーバーに送信するツールがあると便利です。
もう1つは detect です。これは、ターゲットが脆弱かどうかを検出するために、さまざまなHTTPリクエストスマグリング技術を試します。検出アルゴリズムは複雑です。読んでいただき、コメントを送っていただけると幸いです。これは以下の該当セクションで説明されています。
http2smugl requestこのサブコマンドを使用して、(おそらく少し不正な)http2リクエストを送信します。最初のパラメータはURLで、残りは name:value 形式のヘッダーです(コロンの後にスペースがないことに注意)。バックスラッシュエスケープがサポートされています:\r、\n、\xXX エスケープコードを使用できます。例えば、コロンを含むヘッダー名を送信するには、\x3a を使用します(例:name\x3awith\x3acolons:value)。
http2smugl detectこのサブコマンドは、さまざまな技術を使用してHTTPリクエストスマグリングを検出しようとします。使用するには、http2smugl detect [HTTPS URL] を実行するだけです。
このコマンドは、サーバーがスマグルされたヘッダーを解析していると検出できた場合にのみ何かを出力します。これが何を意味するかを理解するには、該当セクションをお読みください。
HTTP/3(quic)の実験的なサポートが実装されています。ただし、HTTP/3に関連するバグを1つも見つけていないため、使用をお勧めしません。
request サブコマンドでHTTP/3を使用するには、URL内の https の代わりに https+h3:// プロトコルを指定します。detect コマンドでも同様にサポートされています。
request サブコマンドには --try-http3 フラグもあります。これは、URLにプロトコルが指定されていない場合(ホスト名のみ)の動作を変更します。フラグが存在する場合、コマンドはコマンドラインまたはターゲットファイル内のそのようなエントリに対して https+h3 プロトコルも試みます(HTTP/2と同様に)。例:http2smugl detect --try-http3 www.example.com はHTTP/3とHTTP/2の両方を試みますが、http2smugl detect --try-http3 https://www.example.com/ はHTTP/2のみを試みます。
このセクションでは、ツールが応答を「区別可能」と言うが、脆弱性が存在し得ないいくつかのケースについて説明します。
AmazonのElastic Load Balancerは、HTTPリクエストスマグリングから複数の緩和策を実装しています。それらのすべてがデフォルトでリクエストを拒否するわけではありませんが、ELBは「疑わしい」ヘッダーを含むリクエストを送信した後に接続を再利用しません。したがって、実際の攻撃は不可能です。
ELBを扱っていることを検出するには、Server 応答ヘッダーを使用できます。フィルタリングされている場合は、content__length ヘッダー(アンダースコア2つに注意)と値 -1 のリクエストを送信できます。400が返ってきた場合、おそらくELBまたは他のWAFです(下記参照)。
Apache Traffic Serverは主にYahooで使用されています。HTTP/2を異例の方法で処理します。メモリ内でHTTP/1.1に変換し、結果のリクエストを再解析します。したがって、技術的にはヘッダーを「スマグル」できたとしても、それが脆弱性につながる方法はありません。同じバイトをHTTP/1.1接続に送信できるからです。
ATSを扱っていることを検出する最も簡単な方法は(Server ヘッダーの他に)、TRACE リクエストを送信することです。リクエストに Max-Forwards: 0 ヘッダーが存在する場合、ATSはデフォルトで TRACE リクエストに応答を返し、バックエンドに転送しません。
Microsoft IISは、HTTP/2ボディ内のチャンクエンコーディングのデコードをサポートしているようです。これは奇妙な動作ですが、セキュリティの観点からは無害です。
IISを扱っていることを検出するには、"transfer-encoding:chunked" ヘッダーと不正なチャンクボディを含むリクエストを送信できます。Server ヘッダーに Microsoft-HTTPAPI のようなものが表示される場合、それが該当します。
WAFは疑わしいヘッダーを検出しようとします。それが彼らの仕事です。その結果、ツールが「区別可能」と言うことがあります。WAFは content_length:-1 のようなものをブロックし、content_length:1 を許可する可能性があります。これは、そのフィルターがたまたまそのような判断を下すためです。
Server ヘッダーにWAFが表示される場合、それはおそらく偽陽性です。
このトピックに関するアイデアがあれば、Twitterの @emil_lerner またはTelegramの @neexemil までご連絡いただくか、GitHubでIssueを投稿してください。