脱・「とりあえずunsafe-inline」。CSPのnonce活用でXSSを根絶する
現場のエンジニア諸君、お疲れ様。
「XSS対策? ちゃんとエスケープしてるし大丈夫でしょ」。そうタカを括っているなら、今すぐ考えを改めたほうがいい。
現代の攻撃者は、君が書いたコードの「エスケープ漏れ」をピンポイントで突くような泥臭いことはしない。「信頼されているはずのライブラリの脆弱性」や「CDN経由で注入された悪意あるスクリプト」を悪用し、ブラウザを完全に制御下に置こうとしてくる。
これを防ぐ最後の砦が、Content-Security-Policy (CSP) だ。今日は、巷に溢れる「とりあえずunsafe-inlineを許可して動かす」という思考停止な運用を卒業し、nonceを用いた真に堅牢な防御の実装を叩き込む。
—
1. なぜ「エスケープ」だけでは不十分なのか?
XSSは反射型、格納型、DOM型と分類されるが、どれも共通のゴールは「攻撃者のスクリプトを、正当なオリジンのコンテキストで実行させること」だ。
もし君がDOM型XSSを仕込まれたらどうなるか? 攻撃者は、君が書いた正規のJSライブラリを悪用してDOMを操作し、認証トークンを外部サーバーへ送信する。このとき、サーバー側でのエスケープ処理は一切役に立たない。
そこで登場するのがCSPだ。「ブラウザに対して、どのソースからのスクリプト実行を許可するか」を厳格にルール化することで、仮に脆弱性があっても、攻撃者のコードを「実行させない」ようにする。
—
2. 禁断の「unsafe-inline」を排除せよ
多くのエンジニアが「動かないから」という理由で unsafe-inline を設定する。これは、警備員がいるはずの銀行の入り口に「誰でも自由に入っていいよ」と看板を掲げるようなものだ。
これを排除するための切り札が、nonce(ナンス:Number used once) だ。
nonceの仕組み
1. サーバーはリクエストごとに、推測不可能なランダム文字列(nonce)を生成する。
2. CSPヘッダに script-src 'nonce-RANDOM_VALUE' を付与する。
3. HTML内の タグにも同じ nonce="RANDOM_VALUE" を付与する。
4. ブラウザは、ヘッダのnonceとタグのnonceが一致するスクリプトのみを実行する。
これなら、攻撃者が勝手に挿入した にはnonceが付与されていないため、ブラウザは無視する。完璧だ。
---
3. 実践:PHPによるセキュアな実装サンプル
では、実務でどう書くか。以下はPHPでの実装例だ。
CSPで守られたページ
---
4. インフラ側でカバーする:Nginx設定のポイント
もしサーバーサイドでヘッダ制御が難しいレガシーな環境なら、WAFやNginxで制御することも検討すべきだ。ただし、Nginx単体では動的なnonce生成は難しいため、基本はアプリケーション側でヘッダを付与し、Nginxは「ベースとなるセキュリティヘッダ」を付与する役割に徹する。
Nginx設定例(最低限のヘッダ):
厳格なデフォルト設定
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-scripts;" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
---
5. 現場のセキュリティ担当者からのアドバイス
最後に、運用上の注意点を3つだけ伝えておく。
1. report-uri / report-to を活用せよ:
CSPでブロックが発生した際、JSONでレポートを送る設定を追加しておこう。開発環境で設定し、何がブロックされているか監視するだけで、君のアプリの「どこが危険な実装なのか」が可視化される。
2. CDNのライブラリにはハッシュを使え:
どうしても外部ライブラリを読み込む必要がある場合、nonceだけでなく sha256-ハッシュ値 を使う手法もある。特に頻繁に更新されないライブラリには有効だ。
3. 「完璧」を目指して止めるな:
最初から厳格なCSPを入れると、サイトが崩れることがある。まずは Content-Security-Policy-Report-Only ヘッダを使用して、ブロックされる内容をログに書き出すことから始めろ。
セキュリティは「魔法の杖」じゃない。「攻撃者がコストを払ってまで狙う価値のない、防御が堅いシステム」を構築し続けることが、我々エンジニアの使命だ。
さあ、今日から unsafe-inline を削除するタスクをバックログに追加してくれ。君のシステムがより堅牢になることを期待している。
コメント