XSSを「過去の遺物」にする:CSPによるモダンな防御戦略とnonceの極意
現場でインシデント対応をしていると、いまだに「XSS対策=htmlspecialchars()でエスケープ」だけで思考停止しているエンジニアに出会う。確かにエスケープは基本だ。だが、今のWebアプリケーションは単なるHTMLの集まりじゃない。複雑なJSライブラリ、外部API、マイクロフロントエンドが入り乱れる中で、開発者がどれだけ完璧にエスケープしようとしても、ヒューマンエラーは必ず起きる。
「コードを書く人間はミスをする」。これがセキュリティの前提だ。だからこそ、ブラウザという防波堤に直接ルールを叩き込む「Content-Security-Policy (CSP)」が重要になる。
今日は、教科書的な説明は抜きにして、実務で明日から使える「突破困難なCSP設計」を叩き込む。
—
1. なぜ「昔ながらのCSP」では防げないのか?
多くの現場で見かけるのが、script-src 'self' https://trusted.cdn.com; といった甘い設定だ。これでは、CDN上の古いライブラリに脆弱性があった場合、あるいは攻撃者がアップロード機能を悪用してCDN上に悪意あるファイルを置いた場合、いとも簡単に突破される。
今、私たちが目指すべきは「Strict CSP(厳格なCSP)」だ。信頼できないスクリプトを一切動かさず、必要なものだけを「nonce(使い捨ての乱数)」を使って許可する。
—
2. 核心:nonceとstrict-dynamicによる防御
nonce(Number used once)とは、リクエストごとに生成されるランダムな文字列のことだ。これを使うことで、ブラウザは「サーバーが発行したnonceを持っているスクリプト以外は無視する」という挙動をとる。
実装のステップ
1. サーバー側でリクエスト毎に cryptographically secure なランダム文字列を生成。
2. HTTPレスポンスヘッダーのCSPにそのnonceを埋め込む。
3. HTML内の タグに nonce="[生成した値]" を付与する。
PHPでの実装例
---
3. インフラレイヤーでの設定(Nginx)
アプリケーションコードでヘッダーを制御するのが理想だが、既存のレガシーシステムで改修が難しい場合は、Nginx側でポリシーを強制することもできる。ただし、nonceは動的である必要があるため、基本的にはアプリ層で管理し、Nginxはヘッダーの付与を担保する役割に徹するべきだ。
Nginxでの基本ポリシー設定例
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-random123'; object-src 'none'; frame-ancestors 'none';";
frame-ancestors 'none' はクリックジャッキング対策として必須
---
4. 現場の盲点:なぜこれで防げるのか?
攻撃者が onclick="alert(1)" や を埋め込んでも、それらには正しいnonceが付与されていない。ブラウザは「サーバーから許可されていない不正なコード」とみなし、コンソールにエラーを吐いて実行を即座に拒否する。
さらに strict-dynamic を組み合わせることで、信頼されたスクリプトが動的にロードするライブラリ(ReactやVueなど)の読み込みも安全に許可できる。つまり、「アプリケーションの柔軟性を殺さずに、攻撃者の自由を奪う」ことができるんだ。
---
5. セキュリティチーフからの「最後の警告」
CSPを導入する際、いきなり厳格な設定を投入して本番環境を壊すのが一番怖い。まずは以下の手順で進めてくれ。
1. CSP Report-Onlyモードを活用する
ヘッダーを Content-Security-Policy-Report-Only に変えることで、ブロックはせずに違反レポートだけを収集できる。
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violation-report-endpoint;
2. レポートを監視する
Sentryや専用のエンドポイントを用意し、本当に必要なスクリプトがブロックされていないか数週間はログを追うこと。
3. 段階的に適用する
問題がないことを確認してから、通常の Content-Security-Policy ヘッダーへ切り替える。
セキュリティは「魔法の杖」じゃない。地道な計測と、ブラウザの強力な機能を使いこなす設計力こそが、我々エンジニアの武器だ。コードを一行書くたびに「これにnonceを付け忘れていないか?」と自問自答する。その習慣こそが、インシデントを防ぐ最後の砦になる。
さあ、次のリリースから早速導入してみよう。何か詰まったら、いつでも相談してくれ。
コメント