CSPの「nonce」こそが、XSSという悪夢を終わらせる最後の砦だ
現場でコードを書いていて、「XSS対策? エスケープ処理は完璧だよ」なんて言葉を耳にすると、私は正直ヒヤリとする。なぜなら、現代のフロントエンド開発において、エスケープ漏れは単なる「ミス」ではなく「必然」だからだ。フレームワークのバグ、サードパーティ製ライブラリの脆弱性、あるいは複雑すぎるDOM操作。これらを完璧に防ぎ切るのは人間業じゃない。
だからこそ、CSP(Content Security Policy)のnonce(ナンス)による制御が重要になる。これは「怪しいコードはそもそも実行させない」という、ゼロトラスト時代の鉄則そのものだ。
1. なぜ「エスケープ」だけでは足りないのか?(PoCのリスク)
例えば、以下のようなコードを考えてみてほしい。
// 攻撃者がURLパラメータを操作できる状況
const username = new URLSearchParams(window.location.search).get("name");
document.getElementById("welcome").innerHTML = "ようこそ、" + username + "さん";
攻撃者が ?name= と送り込めば、ブラウザはそのスクリプトを「信頼されたコードの一部」として実行してしまう。これが反射型XSSの典型だが、怖いのは、攻撃者が動的に外部スクリプトを読み込ませる(DOM型XSS)場合だ。
防御側が「全ての入力値をエスケープする」というルールを徹底していても、一箇所でも v-html や dangerouslySetInnerHTML のような「素通り」ポイントがあれば、ゲームオーバーだ。
2. nonceによる「実行許可リスト」の仕組み
nonce(Number used once)は、リクエストごとに生成される使い捨ての乱数だ。これをHTTPレスポンスヘッダーとHTML内の タグの両方に埋め込む。ブラウザは「ヘッダーの値」と「タグ内の属性値」が一致する場合のみ、そのスクリプトの実行を許可する。
攻撃者が外部から注入した タグには、当然ながら正しいnonce値が含まれていない。結果、ブラウザは「お前は許可されていない」と判断し、実行をブロックする。これが最強の防壁だ。
3. 実装の極意:PHPでの実装サンプル
実務では、nonceはリクエストごとに生成し、サーバーサイドで生成した値をテンプレートエンジンに渡すのが定石だ。
PHP側 (Controller/Middleware)
<?php
// 1. セキュアな乱数を生成(推測不可能であることが重要)
$nonce = base64_encode(random_bytes(16));
// 2. CSPヘッダーを送信
header("Content-Security-Policy: script-src 'nonce-$nonce' 'strict-dynamic'; object-src 'none';");
// 3. テンプレートへ渡す準備
$viewData = ['nonce' => $nonce];
?>
HTMLテンプレート側
<!-- 許可されたスクリプトにはnonce属性を付与 -->
<script nonce="<?php echo htmlspecialchars($nonce, ENT_QUOTES); ?>">
console.log("このスクリプトは実行される");
</script>
<!-- 攻撃者が注入しようとするscriptタグにはnonceがないので実行されない -->
<script>
alert("このコードはブラウザに拒否されるため、実行されない");
</script>
4. Nginxでの設定と運用上の注意点
もしバックエンドが静的コンテンツや、特定のプロキシ層でヘッダーを付与する場合、Nginx側で設定することもある。ただし、nonceは「リクエストごとにユニーク」である必要があるため、Nginx単体で完結させるのは難しい(sub_filterで置換する手法もあるが、推奨はしない)。
基本的には、アプリケーション層で生成し、X-Content-Security-Policy などのヘッダーを付与するのが最も堅牢だ。
設定のポイント(Nginx等でのCSPヘッダーの考え方)
基本的にCSPはアプリケーション側で動的に生成すべきだが、
最低限のポリシーとして以下のように設定する
add_header Content-Security-Policy "default-src 'self'; script-src 'nonce-random123' ...";
※注意: 'nonce-random123'をハードコーディングしてはいけない!
5. 後輩エンジニアへ伝えたいこと
CSPのnonceを導入すると、最初は「インラインスクリプトが動かない!」というトラブルに直面するはずだ。しかし、それは「今まで野放しだったスクリプトが可視化された」という喜ぶべき事態である。
'strict-dynamic'を併用せよ: これを使うと、信頼されたスクリプトが動的に生成したスクリプトも許可されるようになる。SPA(React/Vue/Next.jsなど)との相性が格段に良くなる。- 違反レポート (
report-uri/report-to) を設定せよ: どのスクリプトがブロックされたかをログに吐き出させることで、リリース後の「予期せぬ機能停止」を未然に防げる。
セキュリティは「完璧な防御」を目指すのではなく、「攻撃者のコストを跳ね上げ、ミスが起きても致命傷を避ける仕組み」を作ることだ。CSPのnonceは、そのための最も信頼できる相棒になるはずだ。
明日からの開発で、ぜひ一行の に nonce を足すことから始めてみてほしい。その小さな積み重ねが、あなたのアプリケーションを「難攻不落」へと変えていく。
コメント