【テクニカル・上級編】Content-Security-Policy (CSP) の厳格な設定とディレクティブの最適化 – アプリケーションセキュリティ & 安全な開発防御ガイド

CSPは「設定して終わり」の魔法ではない:ブラウザという名の実行環境を掌握せよ

セキュリティアーキテクトやテックリードの諸君なら、もはや「XSS対策にCSP(Content-Security-Policy)を入れましょう」という初歩的な提言に飽き飽きしているはずだ。しかし、現場のコードベースを覗いてみれば、'unsafe-inline'や'unsafe-eval'が放置され、ただHTTPヘッダーに文字列を流し込んでいるだけの「骨抜きなCSP」がどれほど多いことか。

CSPの真髄は、単なる防御策ではない。「ブラウザ上の実行環境に対する、厳格なランタイム・アクセス制御リスト(ACL)」である。今回は、泥臭いインシデントハンドリングの現場で見えてくる、CSPの「本当の攻防」について深掘りしよう。

—

1. 静的な許可リストから「信頼の連鎖」へ:nonceとstrict-dynamic

かつてのCSP設計は、ドメインのホワイトリストを並べることに終始していた。だが、現代の複雑なフロントエンド環境において、CDNやサードパーティJSのホスト名を並べるのは無意味だ。攻撃者は信頼されたドメインのサブディレクトリや、ライブラリ内の未パッチな脆弱性を突き、その「信頼」を悪用するからだ。

ここで鍵となるのが、'nonce'(Number used once)だ。サーバー側でリクエストごとに生成する暗号学的に安全な乱数をインラインスクリプトに付与し、ヘッダーでその一致を検証する。

推奨する現代的なCSPポリシー設定例

strict-dynamic を活用した現代的かつ堅牢なポリシー例
Content-Security-Policy:
default-src ‘none’;
script-src ‘nonce-EDNnf03nceIOfn39fn3e’ ‘strict-dynamic’ https:;
object-src ‘none’;
base-uri ‘self’;
frame-ancestors ‘none’;
require-trusted-types-for ‘script’;

  • 'strict-dynamic'の威力: これを付与することで、nonceを持つスクリプトが動的に生成・読み込みを行う子スクリプトも自動的に信頼される。これにより、巨大な依存関係ツリーを持つモダンフレームワークでも、カオスなホワイトリスト管理から解放される。
  • 'none'によるデフォルト遮断: default-src 'none'を基点とせよ。明示的に許可しない限り、フォントも画像もコネクションも全て拒否する。ゼロトラストの原則をブラウザ内に持ち込むのだ。

—

2. Trusted Typesによる「DOM XSS」の根絶

どんなに強力なCSPを敷いても、innerHTMLやdocument.writeを介したDOMベースのXSSはすり抜けてくる。これを防ぐのが、現在最も強力な防衛レイヤーである「Trusted Types」だ。

これは、ブラウザのAPI(innerHTML等)に対して、文字列の直接代入を禁止し、「安全であると証明されたオブジェクト」しか受け付けないようにする仕組みである。

// CSPヘッダーで設定: require-trusted-types-for ‘script’;

// ポリシーを作成し、信頼された変換ロジックを定義する
const policy = trustedTypes.createPolicy(‘myPolicy’, {
createHTML: (input) => {
// ここでDOMPurify等を用いたサニタイズを強制する
return DOMPurify.sanitize(input);
}
});

// 以降、安全なオブジェクトのみがDOMに挿入可能となる
element.innerHTML = policy.createHTML(userInput);

このアーキテクチャを導入すれば、開発者がどこでバグを仕込もうとも、攻撃者はDOMの深層で実行されるスクリプトを注入できなくなる。これは生成AIによるコーディング支援が普及した今、「AIが生成した脆弱なコード」を強制的に無害化するガードレイルとして機能する。

—

3. 生成AI時代の新たな脅威:プロンプトインジェクションへの防御層として

今、我々が直面している最大の盲点は「LLMアプリのフロントエンド」だ。ユーザーがプロンプトを通じて出力されたHTMLが、そのままレンダリングされるケースが激増している。

攻撃者は、LLMに細工をして「」という文字列を出力させようとする。この時、バックエンドがどれほど強固でも、ブラウザ側でCSPが適切に設定されていなければ、XSSは完成する。

ここで重要になるのは、「Content-Security-Policyとサンドボックス化されたiframeの組み合わせ」である。

  • sandbox属性の活用: frame-ancestors 'none'は必須だが、ユーザー生成コンテンツを表示するエリアは、sandbox="allow-scripts"のように必要最小限の権限に絞ったiframeに隔離せよ。allow-same-originを付与してはならない。ここを疎かにすると、メインドメインのCookieを攻撃者に奪われる。

—

4. チーフホワイトハッカーからの提言:監査の自動化と「違反レポート」

最後に、CSPを導入して満足している担当者に問いたい。「君たちのCSPは、本当に機能しているのか?」

report-toやreport-uriディレクティブを設定し、違反レポートを収集していないのであれば、それは「盲目的な防御」に過ぎない。現実の世界では、予期せぬスクリプトの読み込みや、ブラウザの仕様変更による誤検知が常に発生する。

  • 違反レポートの可視化: 収集したJSONログをSIEM(SplunkやDatadog)に流し込み、特定のIPやユーザーエージェントからの異常なCSP違反パターンを監視せよ。これは、攻撃者が本格的な攻撃を仕掛ける前の「偵察(プローブ)」を検知する最高のアラートになる。

まとめ:防御の技術は常に「動的」であるべきだ

CSPは静的な設定ファイルではない。アプリケーションの成長と脅威の進化に合わせて更新される、活きたセキュリティポリシーであるべきだ。nonceの運用、strict-dynamicの採用、そしてTrusted TypesによるDOMの保護。これらを組み合わせることで、攻撃者は「ブラウザという名の実行環境」に土足で踏み込むことが不可能になる。

脆弱性を探す側からすれば、CSPが完璧に設定されたサイトほど、攻略しがいがなく、同時に最も美しいアーキテクチャだと感じる。君たちのプロダクトを、そんな「美しい防衛線」にしてほしい。

コメント

タイトルとURLをコピーしました