HTTPレスポンス分割の悪夢:ヘッダインジェクションから身を守る実戦的防衛術
やあ。現場の最前線でコードを書き、インシデントの火消しに奔走しているエンジニア諸君。今日は、Webのアーキテクチャにおいて「忘れ去られた」かのように見えるが、ひとたび火がつけば致命的な被害をもたらす「HTTPヘッダインジェクション」と「レスポンス分割(HTTP Response Splitting)」について話そう。
今どきのWAFが守ってくれるから大丈夫?そう思っているなら、その甘さが脆弱性の入り口だ。攻撃者は、WAFの検知ロジックをすり抜ける「隙間」を常に探している。
1. なぜ「改行」が凶器になるのか
HTTPプロトコルは、ヘッダとボディを \r\n\r\n (CRLF 2回)で区切るという非常にシンプルなルールで動いている。攻撃者の狙いは単純だ。Webアプリケーションがユーザーからの入力を適切にサニタイズせず、HTTPレスポンスヘッダにそのまま埋め込んでしまう箇所があれば、そこに \r\n を注入する。
これにより、Webサーバーが意図しない「2つ目のレスポンス」を生成させることが可能になる。これがレスポンス分割だ。
攻撃のシナリオ:キャッシュ汚染
悪意ある攻撃者が Location: /path?param=%0d%0aSet-Cookie:session=evil のようなリクエストを送ったとする。アプリケーションがこの値をヘッダにセットすると、レスポンスはこうなる。
HTTP/1.1 302 Found
Location: /path?param=
Set-Cookie:session=evil
(ここに本来のレスポンスが続く)
この結果、ブラウザや中間キャッシュサーバーは、この不正な Set-Cookie を正当なヘッダとして解釈する。最悪の場合、キャッシュサーバーがこの偽造レスポンスを保存し、他の全ユーザーのセッションを乗っ取ることができてしまう。これが「キャッシュ汚染」の恐ろしさだ。
2. 「防衛的プログラミング」の鉄則
この脆弱性を防ぐために最も重要なのは、「ユーザー入力には決してCR(\r)やLF(\n)を含めてはならない」という大原則をコードレベルで強制することだ。
PHPでの実装例
PHPでは、header() 関数に渡す値が汚染されていないか厳格にチェックする必要がある。
<?php
// 不正な改行コードを排除するための関数
function safe_header($key, $value) {
// 制御文字や改行コードを徹底的に除去する
$sanitized_value = str_replace(["\r", "\n"], "", $value);
// ヘッダに注入されるリスクを完全に断つ
header("$key: $sanitized_value");
}
// 利用例:ユーザー入力をヘッダに使う際は必ずこの関数を通す
$user_input = $_GET['redirect_url'];
safe_header("Location", $user_input);
?>
Python (Flask) での実装例
現代的なフレームワークは多くの場合、ヘッダ内の改行を検知して例外を投げてくれる。だが、確実を期すならフロントエンド側でバリデーションをかけるのが筋だ。
from flask import Flask, redirect, request, abort
app = Flask(__name__)
@app.route('/redirect')
def safe_redirect():
target = request.args.get('url', '')
# 改行コードが一つでも含まれていればリクエストを拒否する
if any(c in target for c in ['\r', '\n']):
abort(400, description="Invalid characters in header")
return redirect(target)
3. インフラレイヤーでの防御(Nginxの設定)
アプリケーション側での修正はもちろん必須だが、多層防御としてNginx側でも不正なリクエストを弾く設定を入れておくべきだ。
# nginx.conf の http または server ブロックに記述
# 改行コードを含むリクエストを拒否する設定
# 注意: アプリケーションの仕様と照らし合わせて検証すること
if ($request_uri ~* "(\r|\n)") {
return 400;
}
4. 現場のエンジニアへ:教訓とTips
- 入力のホワイトリスト化: 「改行を除去する」だけでなく、「URLとして許可された形式(httpから始まる等)か」を正規表現でチェックする。これが最強の防壁だ。
- WAFだけに頼らない: WAFはパッチを当てるまでの時間稼ぎに過ぎない。根本的な脆弱性はコード内で潰す。これがプロの姿勢だ。
- モダンなライブラリを使う: 古いレガシーなHTTPライブラリには、ヘッダインジェクションに脆弱な実装が残っていることが多い。フレームワークは常に最新版に保て。
攻撃者は常に「設計の想定外」を突いてくる。HTTPヘッダはシステムの屋台骨だ。そこを汚染させることは、システム全体の信頼性を根底から崩すことに直結する。今日のコードレビューから、header() やレスポンス生成ロジックの周辺に \r\n が入り込む隙間がないか、今一度目を光らせてほしい。
堅牢なシステムは、細部への執着から生まれる。健闘を祈る。
コメント