CSPの「nonce」と「hash」:インラインスクリプトを封じ込める最後の砦
セキュリティアーキテクトとして現場に立つと、常に「利便性と安全性のトレードオフ」という亡霊に悩まされる。特にSPA(Single Page Application)が主流となり、バックエンドから動的に注入されるスクリプトや、レガシーなインライン処理が混在する環境では、XSS(クロスサイトスクリプティング)の完全排除は至難の業だ。
多くの開発者は「CSP(Content Security Policy)を設定すれば安全」と信じているが、unsafe-inlineを許可した瞬間にその防波堤は崩壊する。今日は、インラインスクリプトを「許可」しつつも、攻撃者の介入を物理的に不可能にする、nonceとhashを用いた防御アーキテクチャの核心を解剖する。
—
1. なぜ「インライン」がこれほどまでに危険なのか
ブラウザのパーサーは、HTML内のタグをそのまま実行する。攻撃者がXSS脆弱性を突き、任意の文字列をHTMLに挿入した場合、ブラウザはそれが「正当なコード」か「悪意あるコード」かを判別できない。
従来のCSPでは、script-src 'self'と記述することで外部ドメインからの読み込みを制限していた。しかし、多くのCMSや動的生成ページでは、インラインスクリプトが不可欠だ。ここでunsafe-inlineを付与すると、ブラウザはすべてのインラインスクリプトの実行を許可してしまう。これは「鍵をかけずに家を全開にする」のと同義だ。
2. 「nonce(Number used once)」による動的認証
nonceは、リクエストごとに生成されるランダムな文字列(Base64等)だ。サーバー側で動的に生成し、CSPヘッダーとスクリプトタグの両方に埋め込む。
実装ロジック:
1. サーバーがリクエストを受け取る。
2. 暗号論的に安全な乱数生成器でnonceを生成。
3. CSPヘッダーに 'nonce-{random-value}' を設定。
4. HTML内のインラインスクリプトにも nonce="{random-value}" 属性を付与。
ブラウザは、CSPで指定されたnonceと一致しないインラインスクリプトを即座にブロックする。たとえ攻撃者がHTMLを書き換えても、実行時に正しいnonceを知る術はない。
セキュリティヘッダーの例 Content-Security-Policy: default-src 'self'; script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdf'
注意点: nonceをキャッシュ可能なページで使うと、全ユーザーで同じ値が使い回され、無意味になる。必ず「動的生成」であることを保証せよ。
3. 「hash」による静的コードのホワイトリスト化
nonceが「動的」な認証なら、hashは「静的」な認証だ。コードの内容そのものをハッシュ化し、CSP側に登録しておく。
これは、インラインスクリプトの内容が固定されている場合に非常に有効だ。コードを少しでも改ざんすればハッシュ値が変わるため、攻撃者によるペイロードの挿入は即座に検知・ブロックされる。
実装例(SHA-256):
スクリプトの内容をSHA-256でハッシュ化して指定
Content-Security-Policy: script-src 'sha256-n4ePYs6....'
ハッシュは計算コストが低く、CDNでのキャッシュとも相性が良い。しかし、コードを一行修正するたびにハッシュ値を更新しなければならないため、ビルドパイプラインでの自動化が必須となる。
---
4. 現場のアーキテクトが陥る「盲点」
nonceやhashを使っても、防衛が突破されるケースがある。特に注意すべきは以下の2点だ。
- CSPバイパスを狙うGadgetの存在:
CSPを突破する手法として、「Trusted Types」の導入を検討すべきだ。CSPがDOMのソース(例:innerHTMLへの代入)を制御する仕組みだが、これとnonceを組み合わせることで、攻撃者がJavaScriptライブラリの脆弱性を突いてインジェクションを行う「DOM-based XSS」さえも封じ込めることができる。
- 生成AIのプロンプトインジェクション:
最近のトレンドとして、生成AIが生成したHTMLをそのまま表示するアプリケーションが増えている。AIが生成したコードの中に悪意あるスクリプトが混入した場合、CSPのnonceが適切に付与されていなければ、生成AI自体が攻撃の踏み台になる。AIからの出力をサニタイズせず、信頼してCSPの対象に含めるのは論外だ。
結論:多層防御としてのCSP
CSPのnonceとhashは、もはや「あれば望ましい」機能ではなく、現代のWebアプリケーションにおける「標準装備」だ。
1. nonce: 動的に生成されるスクリプト用。
2. hash: 固定のスクリプト用。
3. Strict CSP: 可能な限り 'strict-dynamic' を併用し、信頼されたスクリプトがさらに別のスクリプトをロードできるように制限を制御する。
セキュリティとは、「攻撃を0にすること」ではなく、「攻撃者が動くための余地(アタックサーフェス)を極限まで削り取ること」に他ならない。インラインスクリプトを無作為に許可する時代は終わった。今こそ、ブラウザの強力な実行制限機能をフル活用し、堅牢なアーキテクチャを構築してほしい。
何かあれば、いつでもコードベースの深層で議論しよう。現場からは以上だ。
コメント