HTTP Request Smuggling:現代のWebインフラを食い荒らす「境界線の不一致」
現場でインフラやアプリを見ていると、「境界線」の設計がいかに重要かを痛感する。特に、現代のモダンなWebアーキテクチャでは、フロントエンド(リバースプロキシやCDN)とバックエンド(App Server)が連携してリクエストを捌くのが当たり前だ。
しかし、この「連携」こそが最大の弱点になることがある。それが HTTP Request Smuggling (HRS) だ。
教科書的な定義はさておき、本質的な話をしよう。HRSとは、「フロントエンドとバックエンドの間で、HTTPリクエストの終わりの解釈がズレる」ことによって発生する。このズレが生じると、攻撃者は「リクエストの境界線」を偽装し、後続のユーザーのリクエストを汚染したり、あろうことか別のユーザーのセッションを盗み取ったりすることが可能になる。
なぜ「解釈のズレ」が生まれるのか
HTTP/1.1では、リクエストのサイズを特定するために主に2つのヘッダーを使う。
1. Content-Length (CL): ボディのバイト数を指定する。
2. Transfer-Encoding (TE): chunked を指定することで、ボディを分割して送る。
攻撃者が狙うのは、フロントエンドが CL を見て、バックエンドが TE を見る(あるいはその逆)という構成だ。ここに「不正なヘッダーをわざと混ぜる」ことで、フロントエンドには「一つのリクエスト」として見せつつ、バックエンドには「二つのリクエスト」として送りつけることができる。
攻撃のメカニズム:CL.TE攻撃のPoC
以下は、CL.TE攻撃の概念図だ。
POST / HTTP/1.1
Host: vulnerable-site.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
フロントエンドは CL: 13 を見て、この全体を一つのリクエストとしてバックエンドへ転送する。しかし、バックエンドは TE: chunked を優先する。バックエンドは 0 というチャンク終端信号を受け取った時点で「このリクエストは終わり」と判断する。
問題はその後だ。残された SMUGGLED という文字列は、バックエンドの受信バッファに残ったままになる。次にアクセスしてきた「無実のユーザー」のリクエストは、この SMUGGLED の直後に連結され、バックエンドはそれを一つの連続したリクエストとして処理してしまう。
結果として、ユーザーのセッションクッキーが攻撃者の用意したログサーバーに送信される、といった惨劇が起こる。
防御の鉄則:曖昧さを排除せよ
この脆弱性を封じるための最優先事項は、「HTTP/1.1の曖昧なヘッダー使用を拒否する」ことと、可能な限り 「HTTP/2以降のみを使用する」 ことだ。HTTP/2はバイナリプロトコルであり、リクエスト境界が明確に定義されているため、この種のアタックは構造的に不可能だ。
1. Nginxでの対策(フロントエンド側)
Nginxで、矛盾するヘッダーが含まれるリクエストを厳格に拒否する設定だ。
# /etc/nginx/nginx.conf
# Content-LengthとTransfer-Encodingの両方が存在する場合は拒否
if ($http_transfer_encoding ~* "chunked") {
# chunkedが指定されている場合、CLヘッダーを無視、あるいはエラーにする
}
# 厳格な検証を有効にする(Nginx 1.19.0以降推奨)
# 矛盾するヘッダーや不正なチャンクを拒否する設定
proxy_http_version 1.1;
proxy_set_header Connection "";
# 以下の設定で、不正なリクエストをブロックする
underscores_in_headers off;
ignore_invalid_headers on;
2. アプリケーション層での検証(Python/FastAPI例)
バックエンド側でも、リクエストの妥当性を厳格にチェックするミドルウェアを実装する。
from fastapi import Request, HTTPException, status
async def validate_request_headers(request: Request, call_next):
# CLとTEの両方が存在する場合は脆弱とみなし拒否する
if "content-length" in request.headers and "transfer-encoding" in request.headers:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Request Smuggling attempt detected: Ambiguous headers"
)
response = await call_next(request)
return response
インシデントを防ぐための3つのルール
最後に、現場で戦うエンジニアが守るべき3つのプラクティスを共有しておく。
1. プロトコルの統一: フロントエンドとバックエンドの通信は、可能な限りHTTP/2またはgRPCに統一する。これで境界線の問題は消滅する。
2. インフラのアップデート: 古いApacheやNginxをそのまま使わない。リクエストパーサーの脆弱性は常に修正されている。
3. WAFによる監視: HTTPリクエストのヘッダー異常を検知するルールをWAFに適用しておく。特に Transfer-Encoding を悪用するパターンはWAFのシグネチャで防げるケースが多い。
HRSは単なる設定ミスではなく、プロトコルの仕様の隙間を突く非常に巧妙な攻撃だ。しかし、境界線における「厳格なバリデーション」を徹底すれば、防ぐことは十分に可能だ。
「とりあえず動く」から「正しく安全に動く」へ。その意識の差が、明日起こるかもしれないインシデントを防ぐ唯一の盾になる。コードを書くとき、サーバーを構築するとき、常に「この境界線は本当に曖昧ではないか?」と自問自答してほしい。
コメント