CSPは「魔法の杖」ではない:現場の防衛者が知るべき実戦的境界防御の限界
多くのセキュリティエンジニアが、Content-Security-Policy (CSP) を「XSSを無効化する銀の弾丸」だと誤解している。しかし、現場で数多のペネトレーションテストを潜り抜けてきた人間からすれば、CSPは単なる「設定ファイル」に過ぎない。不適切な設定は、セキュリティの向上どころか、むしろ攻撃者に「どこを重点的に突けばよいか」というロードマップを提示する結果にさえなる。
本稿では、教科書的な説明を排し、モダンなWebアプリケーションにおけるCSPの「防衛アーキテクチャ」としての真価と、その裏側にある脆弱性との戦いについて深掘りする。
—
1. 静的なポリシーの限界とnonceの必然性
かつて主流だった script-src 'self' 'unsafe-inline' のような設定は、もはや無防備に等しい。攻撃者は、アップロードされた画像ファイルの中に仕込んだJavaScriptや、CDN上の古いライブラリの脆弱性を利用して、容易にこの壁を突破する。
現代の防衛において、nonce(Number used once)を用いた動的なポリシー適用は最低限の必須条件だ。これは、サーバーサイドで生成されたユニークなトークンを、リクエストごとにHTMLの script タグに付与し、ポリシー側と照合する仕組みである。
実装例:セキュアなnonceの付与(PHPの例)
<?php
// 1. 各リクエストごとに暗号論的に安全なnonceを生成
$nonce = base64_encode(random_bytes(16));
// 2. レスポンスヘッダーにCSPを設定
header("Content-Security-Policy: default-src 'self'; script-src 'nonce-{$nonce}' 'strict-dynamic'; object-src 'none';");
?>
<!-- 3. 許可されたスクリプトのみにnonceを付与 -->
<script nonce="<?php echo htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8'); ?>">
console.log("このスクリプトはnonceが一致するため実行される");
</script>
ここで重要なのは、'strict-dynamic' の併用だ。これにより、nonceが付与された信頼できるスクリプトが動的にロードした外部スクリプトに対しても、自動的に信頼を継承させることができる。開発効率とセキュリティの妥協点として、現在はこれが最前線である。
—
2. 攻撃者が狙う「盲点」:CSPバイパスのテクニック
CSPを実装しても、以下の「設定の穴」は攻撃者の格好の標的となる。
- JSONPエンドポイントの悪用: サーバー内に存在するJSONPエンドポイントは、CSPを回避して任意のコードを実行するゲートウェイとなる。
script-srcでドメインを許可している場合、そこを足がかりにスクリプトが注入される。 - Baseタグの注入:
<base href="https://attacker.com/">が注入されると、ページ内の相対パスによるリソース読み込みがすべて攻撃者のサーバーへリダイレクトされる。これを防ぐには、CSPでbase-uri 'self'を明示的に設定しなければならない。 - 非直感的な
strict-dynamicの挙動:strict-dynamicを設定すると、多くのブラウザではunsafe-inlineやドメインのホワイトリスト指定が無視される。この挙動を理解せずに古いポリシーを併記すると、意図しない脆弱性が生まれる。
—
3. 生成AI時代のプロンプトインジェクションに対する防衛層としてのCSP
現在、LLMを統合したアプリケーションが増えているが、そこでは「AIが出力した内容がHTMLとしてレンダリングされる」という、極めて高リスクな状況が生まれている。
もしLLMが汚染(Prompt Injection)され、悪意のあるスクリプトを生成した場合、CSPは最後の砦となる。ここでの教訓は、「AIが生成するコンテンツと、アプリケーションのコアロジックを分離せよ」ということだ。
- サンドボックス化: AIの生成物は
iframeで隔離し、sandbox属性を活用する。 - CSPによる実行制御: AIが生成する可能性のあるすべてのスクリプト実行を許可リスト方式で厳格に縛る。
unsafe-evalを含むポリシーは、AI導入環境では絶対に避けるべきだ。
—
4. チーフホワイトハッカーの監査観点:監視とレポート
CSPを実装して終わりでは、プロとは呼べない。必ず report-to または report-uri を使用して、ポリシー違反を継続的に監視する仕組みを構築すること。
私がペネトレーションテストを行う際、まず見るのはクライアントのCSP違反レポートだ。そこには、開発者が意図していない「野良スクリプト」の挙動や、サードパーティ製ツールの不審な通信が克明に記録されているからだ。
推奨されるCSPヘッダー構成(防御のベストプラクティス)
# 厳格なCSPの例
Content-Security-Policy:
default-src 'self';
script-src 'nonce-rAnd0m' 'strict-dynamic' 'http:' 'https:';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
report-uri /csp-violation-report-endpoint;
結論:
CSPは静的な設定ではなく、アプリケーションのライフサイクルと共に進化するべき「生きた防衛策」である。攻撃者は常に「ホワイトリストの隙間」を探している。君たちがやるべきことは、攻撃者の思考を先回りし、nonce と strict-dynamic を軸とした強固なコンテキストを構築することだ。
もし君のプロジェクトで、まだ unsafe-inline が許容されているなら、今すぐそれを剥がすプランを立てろ。それが、プロのエンジニアとしての最低限の責務である。
コメント