モダンなWebアーキテクチャに潜む見えない隙間
モダンなWebアプリが単独で動作することは稀です。通常は、ロードバランサー、Webアプリケーションファイアウォール(WAF)、そしてNginxやHAProxyのようなリバースプロキシといった複雑なスタックの背後に配置されています。この構成はパフォーマンスを向上させますが、一方で「デシンクロナイゼーション(同期不全)」という危険な隙間を生み出します。もし、これらのサーバー間でリクエストの終端と次の開始位置の解釈が1バイトでも異なれば、HTTP Request Smuggling(HTTPリクエスト・スマグリング)の脆弱性が生じます。
最近、私は高トラフィックな環境の監査を行いました。そこではWAFが厳格に設定されており、/adminパスへのアクセスはすべて403 Forbiddenを返すようになっていました。外部から見れば、セキュリティ態勢は強固に見えました。しかし、フロントエンドプロキシとバックエンドサーバーによる競合ヘッダーの解釈の違いを突くことで, このフィルタリングを完全にバイパスすることができました。
WAFにはホームページへの無害な200 OKリクエストに見えていましたが、バックエンドは管理ダッシュボードへの「スマグリング(密輸)」されたコマンドを受け取っていました。これは単純なコーディングミスではなく、2つの異なるサーバー間の通信における根本的な崩壊だったのです。
根本原因:CLとTEの曖昧さ
HTTP/1.1では、メッセージの長さを定義する方法が2つあります。一般的に使われるのは、ボディのサイズをバイト数でカウントするContent-Length(CL)です。もう一つは、メッセージを小さな断片に分割するTransfer-Encoding: chunked(TE)です。問題は、攻撃者がこれら両方を含むリクエストを送信したときに始まります。
フロントエンドサーバーがCLヘッダーを優先し、バックエンドがTEヘッダーを優先する場合、データストリームに対する両者の認識が乖離します。フロントエンドは、単一のリクエストとみなしたデータブロックを転送します。しかし、バックエンドはチャンク形式のメッセージが途中で終わったと判断し、残りのデータを2つ目の別のリクエストの開始として扱います。この「残された」データが、サーバーの処理バッファに「スマグリング(密輸)」されるのです。
3つの主なバリアント
- CL.TE: フロントエンドが
Content-Lengthを使用し、バックエンドがTransfer-Encodingを使用する。 - TE.CL: フロントエンドが
Transfer-Encodingを使用し、バックエンドがContent-Lengthを使用する。 - TE.TE: 両方のサーバーが
Transfer-Encodingをサポートしている。しかし、攻撃者がTransfer-Encoding: xchunkedのようなバリエーションを用いて一方のサーバーからヘッダーを隠し、強制的にContent-Lengthへフォールバックさせる。
サーバーの要塞化には、単にヘッダーを修正する以上のことが求められます。それは基本的な衛生管理から始まります。私が本番環境を構築する際は、すべてのサービスアカウントに toolcraft.app/ja/tools/security/password-generator のパスワード生成ツールを使用しています。これはブラウザ内でローカルに動作するため、機密性の高い文字列がネットワークに流れることはありません。しかし、強力な認証情報があったとしても、プロトコルの欠陥があれば、攻撃者はインフラ自体を欺いて「表玄関」から侵入できてしまうのです。
実践:Burp Suiteを使用したCL.TEの悪用
これをテストするには、HTTPヘッダーを自動的な「修正」なしに生の状態で操作できるツールが必要です。Burp Suiteはこの分野の業界標準です。CL.TEのシナリオでは、フロントエンドにはリクエストが長いと思わせ、バックエンドには早く終わったと思わせることが目標になります。
攻撃ペイロード
制限された/adminエンドポイントにアクセスしたい場合、フロントエンドに以下のような特定のペイロードを送信します。
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 139
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-site.com
Foo: x
同期不全(デシンクロ)の仕組み:
- フロントエンド (CL): 139バイトを読み取ります。これには
0からFoo: xまでのすべてが含まれます。そして、そのブロック全体をバックエンドに渡します。 - バックエンド (TE): チャンクを探します。
0の後に空行があるのを見て、リクエストが終了したと判断します。 - 汚染されたバッファ: バックエンドは
0で停止します。残りのバイト(GET /adminの部分)はネットワークバッファに待機したままになります。 - 影響: 次のユーザーがリクエストを送信すると、バックエンドはそのスマグリングされた
GET /adminを、正規のユーザーリクエストの先頭に結合します。これで、次の接続を正常にハイジャックしたことになります。
自動検出には、BurpのHTTP Request Smuggler拡張機能が不可欠です。バイトオフセットの計算や、さまざまな難読化手法のテストといった退屈な作業を処理してくれます。
実践的な防御:スタックの要塞化
この問題の修正は、単一のパッチを当てることではありません。アーキテクチャの一貫性が必要です。ここでは、私が本番環境でスマグリングを根本から断つために使用している3つの戦略を紹介します。
1. HTTP/2への移行
最も効果的な修正策は、チェーン全体でHTTP/2に移行することです。HTTP/2は、メッセージの境界を定義するためにテキストベースのヘッダーではなく、バイナリフレーミングメカニズムを使用します。これにより、CLとTEの間の曖昧さが完全に解消されます。内部でHTTP/1.1を使用せざるを得ない場合は、フロントエンドプロキシがリクエストをダウングレードする前に厳格な検証を行うようにしてください。
2. バックエンドでのChunked Encodingの無効化
APIが大規模なデータセットのストリーミングを必要としない場合は、内部サーバーでTransfer-Encodingを無効にします。ほとんどのモダンなWebサーバーは、両方のヘッダーを含むリクエストを拒否するように設定できます。例えば、Nginxの新しいバージョンでは、デフォルトでこれらを拒否することが多いですが、特定のビルドで常に確認する必要があります。
3. 要塞化されたリバースプロキシのデプロイ
EnvoyやHAProxyの最新バージョンを使用してください。これらはプロトコル標準に対して「オピニオン(厳格な見解)」を持つように設計されています。アプリケーションロジックに到達する前に、不正な形式や曖昧なリクエストを破棄します。
# Nginxの要塞化ロジックの例
http {
# アンダースコアや無効な文字を含むヘッダーを拒否する
ignore_invalid_headers on;
# プロキシが曖昧なエンコーディングヘッダーを透過させるのを防ぐ
proxy_hide_header Transfer-Encoding;
}
まとめ
HTTP Request Smugglingは、自身のインフラを自分自身に対して牙を剥かせる、非常に影響の大きい脅威です。プロキシとバックエンドによるヘッダー解釈の不一致を理解することで、攻撃者に発見される前にこれらの隙間を埋めることができます。スタック全体でプロトコルの一貫性を保つことを目指しましょう。WAFが見ているものが、アプリケーションサーバーが見ているものと同じであるとは決して想定しないでください。

