【実務・中級編】HTTPにおける改行コード(CRLF)の仕様 – HTTPプロトコル・通信規格実践ガイド

なぜ「たかが改行」が世界を壊すのか?HTTP/1.1におけるCRLFの深淵

ネットワークエンジニアとして現場を歩いていると、「なぜか特定の環境でだけレスポンスが化ける」「認証ヘッダーが正しく認識されない」といった、一見すると不可解なトラブルに遭遇することがあります。その原因の多くは、実はプロトコルの根幹を支える「たった2バイトの制御コード」に隠されています。

今回は、HTTP/1.1の屋台骨であり、かつセキュリティの急所でもある「CRLF(`\r\n`)」について、仕様と実務的リスクの観点から深掘りしていきましょう。

HTTPの言語:CRLFという「区切り線」

HTTP/0.9からHTTP/1.1に至るまで、HTTPはテキストベースのプロトコルとして進化してきました。人間が読める平易な形式である一方、機械にとっては「どこまでがヘッダーで、どこからがボディか」を厳密に判定する必要があります。

そこで登場するのが、`CR` (Carriage Return: `0x0D`) と `LF` (Line Feed: `0x0A`) のペア、すなわち `\r\n` です。

RFC 7230(HTTP/1.1の仕様)において、このCRLFは単なる改行ではありません。メッセージの区切りを定義する「境界線」そのものです。サーバーはストリームとして流れてくるバイト列を読み込み、`\r\n` を見つけるたびに「あ、ここでヘッダー行が終わったな」と判断します。

通信フローにおける役割

1. リクエストライン: `GET /index.html HTTP/1.1\r\n` で終了を合図。
2. ヘッダーセクション: `Host: example.com\r\n` のように、各ヘッダーを1行ずつ定義。
3. ヘッダーの終端: `\r\n` (空行)を検知し、これ以降がメッセージボディであると特定。

この「厳密な区切り」が崩れると、パーサーは同期を失います。これが、通信の不整合やセキュリティインシデントの火種となります。

セキュリティの暗部:HTTPレスポンス分割攻撃

CRLFの仕様を逆手に取った最も有名な脆弱性が「HTTPレスポンス分割攻撃 (HTTP Response Splitting)」です。

例えば、Webアプリがユーザー入力をそのままヘッダーに含める際、悪意あるユーザーが `\r\n` を注入したとします。

// 悪意のある入力例: “admin\r\nSet-Cookie: session=evil”
// サーバーがこれをそのままヘッダーに埋め込むと…
HTTP/1.1 200 OK
User-Header: admin
Set-Cookie: session=evil
Content-Length: 0

本来1つのレスポンスであったはずが、サーバー側の解釈によって「2つのレスポンス」に見えてしまう可能性があります。これにより、キャッシュ汚染やクロスサイトスクリプティング(XSS)へのエスカレーションを招きます。現代のWAFやフレームワークはこの入力をサニタイズ(無害化)しますが、バックエンドのレガシーなAPIサーバーでは今もなおリスクが残っています。

デバッグと検証:現場で使う「生パケット」の確認術

「仕様通り動いているはずなのに、なぜかエラーが出る」という場合、ヘッダーに意図しない改行が紛れ込んでいないか、`curl` で生データを確認するのが鉄則です。

1. curlで改行コードを含めた生データを確認

`-v` オプションだけでなく、`–trace-ascii` を使うと、隠れた制御コードまで可視化できます。

通信の生バイト列を詳細に追跡する
curl -v –trace-ascii debug.dump https://api.example.com/v1/resource

`debug.dump` ファイルを開くと、`0d 0a` というバイト列が期待通りの場所に存在するかを確認できます。

2. PythonでHTTPリクエストを自作して実験する

高レベルなライブラリ(requests等)を使うと改行コードは自動制御されますが、あえて低レイヤーで実験すると理解が深まります。

import socket

生ソケットでHTTPリクエストを送信する実験
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((“example.com”, 80))

意図的にCRLFを挿入してリクエストを送る
request = (
“GET / HTTP/1.1\r\n”
“Host: example.com\r\n”
“User-Agent: Debug-Tool/1.0\r\n”
“\r\n” # 最後に空行(CRLF)が必須
)
s.sendall(request.encode(‘utf-8’))
print(s.recv(1024).decode(‘utf-8’))
s.close()

運用上のTips:エンジニアへの教訓

現場でトラブルシューティングを行う際、以下の3点だけは常に意識してください。

1. 改行コードの統一: Windows環境(`\r\n`)からLinux上のAPIサーバーに設定ファイルやスクリプトをアップロードする際、改行コードが混在するとパースエラーになることがあります。Gitの `core.autocrlf` 設定は軽視してはいけません。
2. ホワイトリスト検証: ヘッダーに含める値に `\r` や `\n` が含まれていないか、入力バリデーションの段階で徹底的に弾くこと。これが最大の防御です。
3. プロキシの挙動: NginxやApacheなどのリバースプロキシを介す場合、プロキシ側が「不正な改行」を検知して400 Bad Requestを返すことがあります。ログに400が頻出する場合、クライアントのヘッダー不備を疑うのが定石です。

HTTP/1.1は古くからあるプロトコルですが、その仕様の端々に「インターネットの黎明期」の知恵とリスクが詰まっています。皆さんが書くコードの一行一行、そしてパケットの2バイトが、今日も世界の通信を支えていることを忘れないでください。

何かトラブルがあれば、まずは `\r\n` の位置を疑う。これが、百戦錬磨のエンジニアへの第一歩です。

コメント

タイトルとURLをコピーしました