なぜ「HTMLエスケープ」だけでは防げないのか?――コンテキスト依存エスケープの極意
現場でエンジニアから「ちゃんとエスケープ処理は入れています」という報告を受けるたび、私はこう聞き返す。「どのコンテキストで、どうエスケープした?」と。
多くの開発者が陥る罠は、htmlspecialchars() や DOMPurify を「とりあえず通しておけば安心」という魔法の杖のように扱っていることだ。だが、現実はそんなに甘くない。HTMLボディ(タグ間)で安全な文字が、ブロック内やCSSプロパティ内では凶器に変わる。
今日は、インジェクション攻撃の核心である「コンテキスト依存エスケープ」の重要性と、現場で即戦力となる実装の勘所を叩き込む。
---
1. なぜ「場所」によって攻撃手法が変わるのか(PoCの視点)
攻撃者は、ブラウザがその文字列を「データ」として解釈するか、「コード(命令)」として解釈するかという、解析エンジンの隙を突いてくる。
例えば、'"> という入力を単にHTMLエスケープしても、以下のような場所では無力だ。
- JavaScript内:
var name = '{{user_input}}'; - ユーザー入力が
' ; alert(1); //だった場合、エスケープが不十分だとJSのコンテキストを脱出して任意コードが実行される。 - CSS内:
javascript:alert(1)やurl("javascript:...")を注入されれば、スタイル指定の隙を突いてXSSが発火する。これらはHTMLの「文字実体参照」では防げない。それぞれのコンテキストに合わせた「意味のある無害化」が必要なのだ。
---
2. 実践的セキュア実装:現代のテンプレートエンジンの流儀
現代の開発では、自前で
str_replaceを書くような泥臭いことはせず、「コンテキスト認識型」のテンプレートエンジンを正しく設定することが、最もコスパの良い防御策だ。例:Jinja2 (Python) での防衛策
Jinja2はデフォルトでHTMLエスケープが有効だが、JavaScript内に埋め込む際は明示的な制御が必要だ。
悪い例: JSコンテキストでHTMLエスケープだけでは不十分
良い例: tojson フィルタを使用してJSエスケープを強制する
これにより、クォートやバックスラッシュが適切にエスケープされ、JSの文字列として安全になる
例:PHPでの実装(htmlspecialcharsの限界を超えて)
PHPで属性値やJS内に埋め込む場合、単純な
ENT_QUOTESだけでは不十分なケースがある。';
// JavaScript内への埋め込みは、json_encodeを通すのが最も安全
// 制御文字やタグ終端文字を安全に変換してくれる
$js_var = json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_HEX_APOS);
echo "";
?>---
3. 防御の最後の砦:Content Security Policy (CSP)
どれほどコードを精査しても、ゼロデイやヒューマンエラーはゼロにはできない。だからこそ、ブラウザ側に「どこから来たスクリプトなら実行していいか」を教える CSP (Content Security Policy) が不可欠だ。
以下のNginx設定を適用するだけで、万が一インジェクションを許してしまっても、悪意のあるインラインスクリプトの実行を大幅に制限できる。
Nginx設定例: レスポンスヘッダーにCSPを追加
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none';";
解説:
default-src 'self': 自分のドメイン以外からの読み込みを禁止
script-src 'self': 外部からのスクリプト読み込みを禁止し、インラインスクリプトも原則禁止
object-src 'none': プラグイン実行を禁止---
4. プロとして、明日からやるべきこと
1. テンプレートエンジンの設定を確認せよ: 自分が使っているフレームワーク(Laravel/Blade, React, Vue, Jinja2等)が、デフォルトでコンテキストを自動判別しているか確認する。
2. 「出力時」にエスケープする: 入力値のバリデーションは「形式チェック」であり、エスケープは「出力対策」だ。この分離を徹底せよ。
3. データ型を意識せよ: 文字列、数値、配列。型を厳密に扱うことで、型変換に伴う思わぬエスケープ漏れを防げる。セキュリティは「完璧なコード」を書くことではなく、「どこから破られても致命傷にならない設計」をすることにある。この記事を読んだ君なら、今日からコードレビューの視点が変わるはずだ。次は、君のチームのコードからインジェクションの芽を摘み取ってきてくれ。期待している。
コメント