終わりのない「スクリプトのホワイトリスト地獄」から抜け出す:CSP strict-dynamic の真髄
現場でインシデント対応をしていると、よく耳にする悲鳴がある。「CSP(Content Security Policy)を導入したら、サードパーティのタグマネージャーや動的読み込みのライブラリが全部弾かれてサイトが真っ白になった。だから結局 unsafe-inline を許可して緩和した」という話だ。
これでは、防弾チョッキを着るために胸元を大きく切り開いているようなものだ。現代のフロントエンド開発において、すべてのインラインスクリプトを個別にハッシュ化したり、ドメイン単位でホワイトリストを管理したりするのは、現実的ではない。
そこで我々が使う切り札が、CSP Level 3の strict-dynamic だ。今日は、これがなぜ「インジェクション耐性の最終防衛ライン」になり得るのか、実務的な実装とともに紐解いていこう。
—
なぜ従来のCSPは「運用破綻」するのか
従来のCSP(Level 2まで)では、スクリプトを許可するために script-src 'self' https://trusted.com のようにドメインを指定する必要があった。しかし、モダンなWebアプリでは、メインのスクリプトが別の小さなスクリプトを動的に生成(document.createElement('script'))してロードすることが一般的だ。
攻撃者は、この「動的な読み込み」の隙を狙う。XSSでインラインスクリプトを注入できれば、たとえドメイン制限があっても、信頼できるホストを経由して悪意のあるコードをロードさせることが可能になるからだ。
strict-dynamic を使うと、ポリシーはこう変わる。
「最初に読み込まれた信頼できるスクリプトが、その後に生成するスクリプトは、たとえ外部ドメインであっても信頼する」
これにより、ドメインのホワイトリスト管理という泥沼から解放される。
—
実践:strict-dynamic を活用した堅牢な実装
strict-dynamic を使用する際は、古いブラウザとの互換性も考慮しつつ、ノンセ(nonce: 乱数)を組み合わせるのが定石だ。
1. Nginxでのレスポンスヘッダー設定例
まずは、インフラ側でポリシーを強制する。Nginxの設定例だ。
厳格なCSPヘッダーの設定
‘nonce-…’ は毎回サーバーサイドで生成し、HTMLのscriptタグにも付与する必要がある
add_header Content-Security-Policy “default-src ‘self’; script-src ‘nonce-random123’ ‘strict-dynamic’; object-src ‘none’; base-uri ‘self’;”;
2. サーバーサイドでのNonce生成(PHPの例)
Nonceはリクエストごとに生成し、推測不可能な値でなければならない。
—
攻撃者が狙う盲点と「防御の掟」
strict-dynamic を使えば無敵というわけではない。エンジニアが陥りがちなミスがいくつかある。
1. unsafe-inline との併用:
strict-dynamic が指定されている場合、モダンブラウザは unsafe-inline を無視する仕様になっている。しかし、古いブラウザ向けに併記すると、ブラウザによってはセキュリティが骨抜きになる可能性がある。常に strict-dynamic を優先し、古いブラウザへの配慮は nonce で行うのが鉄則だ。
2. Nonceの使い回し:
もし nonce が固定値(静的な値)であったり、推測可能であった場合、攻撃者はその値をHTMLに注入するだけでCSPを突破できる。必ずリクエストごとに生成すること。
3. 「信頼できるスクリプト」の選定:
strict-dynamic は、最初に読み込まれたスクリプトが「汚染」されていれば、その後に続くすべてのスクリプトに権限を与えてしまう。自社でホストしているメインのJavaScriptファイルにXSS脆弱性がないか、静的解析ツール(SnykやSonarQubeなど)で徹底的に叩く必要がある。
—
現場のリーダーから若手へ:設計の心得
最後に一つだけ伝えておきたい。セキュリティは「設定して終わり」のチェックリストではない。「何が信頼の起点(Trust Anchor)なのか」を定義する行為そのものだ。
strict-dynamic を導入するということは、「自分たちの書いたメインのJSファイルを、何があっても脆弱にしない」という覚悟を決めることに他ならない。運用でカバーしようとせず、プロトコルとブラウザの機能を信じ、コードの質で守り抜く。それが、真にプロフェッショナルなエンジニアの立ち振る舞いだと僕は思う。
もし明日、君たちのプロジェクトで「CSPで詰まった」という声が聞こえたら、この記事を読み返してほしい。そして、妥協して unsafe-inline を入れるのではなく、ポリシーを設計し直す勇気を持ってほしい。
現場からは以上だ。コードの安全を祈る。
コメント