HTTPヘッダーインジェクション:その「改行」がシステムを陥落させる
現場でインシデント対応をしていると、「まさか、そんなところで?」という箇所で足元をすくわれるケースによく遭遇します。その筆頭格がHTTPヘッダーインジェクションだ。
多くのエンジニアは「SQLインジェクション」には過敏だが、HTTPヘッダーに対しては驚くほど無頓着だ。ユーザーからの入力をそのままレスポンスヘッダーに流し込む。この「何気ない実装」が、実は攻撃者にシステムをコントロールする鍵を渡していることに気づいていない。
今日は、HTTPヘッダーインジェクションの本質と、それを今日から根絶するための実務的なテクニックを共有する。
—
1. なぜ「改行コード」だけでシステムが崩壊するのか
HTTPプロトコルにおいて、ヘッダーとボディは \r\n\r\n (CRLF) で区切られている。攻撃者はこの仕様を悪用する。
例えば、ユーザーの入力値が以下のようにヘッダーに反映されるコードがあったとしよう。
// 脆弱な実装例:ユーザー入力をそのままヘッダーにセット
header(“X-User-Name: ” . $_GET[‘name’]);
もし、攻撃者が name パラメーターに admin\r\nSet-Cookie: session=evil という文字列を送ったらどうなるか?
サーバーが生成するレスポンスはこうなる。
HTTP/1.1 200 OK
X-User-Name: admin
Set-Cookie: session=evil
Content-Type: text/html
(ボディ内容…)
ブラウザやキャッシュサーバーは、この改行によって「あ、ここでヘッダーが終わって、次からボディだな」と誤認する。これがHTTPレスポンス分割攻撃(HTTP Response Splitting)の正体だ。キャッシュ汚染(Cache Poisoning)を引き起こせば、特定のユーザーのセッションを乗っ取ったり、偽のコンテンツを全ユーザーにバラ撒いたりすることが可能になる。
—
2. 【実務編】言語別:安全な実装サンプル
一番の対策は「ユーザー入力をそのままヘッダーに出力しない」ことだ。しかし、どうしても動的な値を設定せざるを得ない場合は、以下の実装をテンプレートとして使ってほしい。
PHPでの対策
PHP 5.1.2以降であれば、header()関数は改行コードが含まれるとエラーを出す仕様になっているが、ライブラリ層で迂回されるリスクを考慮し、明示的なサニタイズを推奨する。
function set_safe_header($name, $value) {
// 改行コード(CR/LF)を強制的に除去
$sanitized_value = str_replace([“\r”, “\n”], ”, $value);
// 念のためヘッダー名もバリデーション
if (preg_match(‘/^[a-zA-Z0-9-]+$/’, $name)) {
header(“$name: $sanitized_value”);
}
}
Python (Flask) での対策
Flask等のフレームワークを使う場合、make_response を経由してヘッダーをセットするのが定石だ。
from flask import make_response, request
@app.route(‘/set-header’)
def set_header():
user_input = request.args.get(‘name’, ”)
# 改行コードを削除してセット
safe_value = user_input.replace(‘\r’, ”).replace(‘\n’, ”)
response = make_response(“Header Set”)
response.headers[‘X-User-Name’] = safe_value
return response
—
3. インフラ層での防御(Nginxの設定)
アプリケーションコードの修正が追いつかない場合、あるいは多層防御を構築したい場合、Nginxの ngx_http_headers_module ではなく、WAFやリバースプロキシで「不正なヘッダー」を遮断する。
Nginxで特定の異常なヘッダーを拒否する設定例だ。
nginx.conf の http または server ブロック内
改行コードを含むリクエストヘッダーを拒否する(デフォルトで有効だが明示的に意識する)
underscores_in_headers off;
さらに厳格にいくなら、ModSecurity などのWAFを導入し以下のルールを適用する
SecRule RESPONSE_HEADERS “[\r\n]” “phase:3,id:1000,deny,status:500,msg:’Header Injection Attempt'”
—
4. チーフエンジニアからの「鉄の掟」
最後に、現場で設計する際に守ってほしい3つのルールを伝える。
1. ホワイトリスト方式を貫け: ヘッダー値に動的な値を入れる際、それが期待される形式(英数字のみ、特定のIDのみ等)か確認するバリデーションを必ず通すこと。正規表現 ^[a-zA-Z0-9\-_]+$ 程度は必須だ。
2. ヘッダーへの直接注入を避ける: 可能であれば、ユーザーからの入力値をそのままヘッダーに含める設計自体を廃止しろ。「どうしても必要か?」を自問自答せよ。
3. WAFは最後の砦: アプリが脆弱でもWAFで防げることはある。しかし、WAFだけに依存してコードを放置するのは、鍵のかかっていない玄関で警備員を雇うようなものだ。
セキュリティは「魔法のツール」で解決できるものではない。泥臭いコードの積み重ねと、仕様への深い理解が、君たちのプロダクトを唯一守ることができる。
次回のコードレビューで、もしヘッダー操作を見かけたら、この「改行」のリスクを思い出してほしい。その一行の修正が、重大なインシデントを未然に防ぐことになるのだから。
コメント