HTTP/1.1の「境界線」を巡る攻防:Content-Lengthが語る信頼と裏切りの物語
ネットワークエンジニアとして現場を歩いていると、「なぜかリクエストが途中で切れる」「妙なレスポンスが混入する」といった、仕様書の端っこに書かれたような挙動に足元をすくわれる経験を一度はするはずだ。
HTTP/1.1の根幹を支える `Content-Length` ヘッダー。これは単なるバイト数の宣言ではない。クライアントとサーバーが「どこまでがメッセージか」という共通認識を持つための、極めて重要な契約書だ。今日は、この契約が破綻した時に何が起きるのか、そして私たちがどうやってこの「境界線」を守るべきかを深掘りしていこう。
—
1. 境界線の定義:なぜContent-Lengthが必要なのか
HTTP/1.1のメッセージボディは、ストリームとして送られてくる。TCPのパケットはネットワークの都合で分割・再構成されるため、受信側は「どこでメッセージが終わるのか」を自律的に判断しなければならない。
ここで登場するのが `Content-Length` だ。RFC 9112(旧RFC 7230)では、このヘッダーがエンティティボディの長さをオクテット(バイト)単位で正確に示すものと定義されている。
通信フローの理想像
1. クライアント: `Content-Length: 123` をヘッダーに付与し、ボディを送出。
2. サーバー: 受信したバイト数が123に達した時点で「メッセージ完了」と判断し、次の処理(パースや後続リクエストの待機)へ移行。
もしこの数値が間違っていたら? サーバーは「まだ来るはずだ」と待ち続け(タイムアウト待機)、あるいは「もう終わったはずだ」と誤認して後続のデータを誤ったメッセージの一部として処理してしまう。これが、HTTP Request Smuggling(HTTPリクエストスマグリング)の温床となる。
—
2. 実践:Content-Lengthを意識したデバッグ術
現場で最も多いトラブルは「ヘッダー値と実データ長の不一致」だ。特にマルチバイト文字を扱う際、文字数とバイト数を混同してはいけない。
curlによる挙動確認
手始めに、故意に長さを偽装したリクエストを投げてみよう。
実際には「Hello」の5バイトだが、長さを10と偽装する
curl -v -X POST http://example.com/api/echo \
-H “Content-Length: 10” \
-d “Hello”
サーバーはこのリクエストを受けたとき、`Content-Length: 10` を見て「あと5バイト来るはずだ」と待ち続ける。結果、サーバー側で `408 Request Timeout` や、極めて長い待機時間が発生する。これがサービス拒否攻撃(DoS)の第一歩だ。
—
3. Web API開発における実装の罠:Fetch APIの挙動
モダンなフロントエンド開発では `fetch` を使うことがほとんどだろう。実は、`fetch` は `Content-Length` を自動的に計算・付与する。ここで開発者が手動でヘッダーを上書きしようとすると、ブラウザのセキュリティポリシーによってブロックされるか、不正なリクエストとして弾かれる。
// fetchは自動でContent-Lengthを計算するため、手動指定は避けるべき
const bodyData = JSON.stringify({ message: “Hello World” });
fetch(‘/api/data’, {
method: ‘POST’,
headers: {
// 自分で Content-Length を計算してセットするのは非推奨
// ‘Content-Length’: bodyData.length, // <- これをするとバグの元
'Content-Type': 'application/json'
},
body: bodyData
});
サーバーサイド(Node.js/Express等)では、`body-parser` や `express.json()` ミドルウェアがこれらのヘッダーを解析する。インフラ運用者は、WAF(Web Application Firewall)がこの `Content-Length` を正しく監視し、不自然な巨大値や不一致をブロックしているか確認する必要がある。
---
4. セキュリティリスク:境界線を越える悪意
`Content-Length` と `Transfer-Encoding: chunked` が混在した場合、HTTP/1.1の仕様では前者を無視して後者を優先せよとある。しかし、フロント側のリバースプロキシ(Nginxなど)とバックエンドのアプリケーションサーバーで、この解釈が食い違ったらどうなるか?
- フロント(Nginx): `Content-Length` を信じて処理。
- バックエンド: `Transfer-Encoding` を信じて処理。
この「解釈のズレ」を突かれると、一人のユーザーが送ったリクエストが、別のユーザーのセッションを汚染する「リクエストスマグリング」が成立する。
対策のチェックリスト
1. HTTP/2 への移行: HTTP/2以降はバイナリフレーム化されており、`Content-Length` に依存しない境界線管理が行われる。可能な限り HTTP/2 以上のプロトコルを利用すること。
2. 正規化の徹底: リバースプロキシ(Nginx)でヘッダーの重複や矛盾するヘッダーを拒否する設定を行う。
# Nginx設定例: 不正なヘッダーを弾く
proxy_set_header Content-Length $content_length;
if ($http_transfer_encoding ~ “chunked”) {
proxy_set_header Content-Length “”;
}
—
まとめ:エンジニアとして「データ長」を信頼しすぎないこと
プロトコルの仕様書は、性善説で書かれていることが多い。しかし、ネットワークの現場は常に「性悪説」で動くべきだ。
`Content-Length` は便利なツールだが、同時に攻撃の入り口にもなる。APIを設計する際は、巨大なペイロードを一度にメモリに載せない(ストリーミング処理を検討する)、プロキシとアプリ間でヘッダーの解釈を統一する、そして何より「プロトコルの境界線を正確に守る」という意識を持つこと。
それが、堅牢なインフラを構築する第一歩だ。今日のデバッグログに現れたその `Content-Length` の数値、もう一度疑ってみてほしい。そこには、まだ見ぬバグが隠れているかもしれないのだから。
コメント