CSPは「魔法の杖」ではない:インラインスクリプト制限の先にある防御の深淵
多くのエンジニアが「CSP(Content Security Policy)を設定しました」と胸を張るが、実際のところ、その設定が攻撃者のクリエイティビティをどれだけ阻害できているか、真剣に考えたことはあるだろうか?
XSS(Cross-Site Scripting)の文脈において、CSPはもはや単なる「お守り」ではない。しかし、unsafe-inlineを安易に許可し、unsafe-evalでJavaScriptの柔軟性を担保しようとする現場のコードを見るたびに、我々は「防壁の中に最初から穴を開けている」という事実に直面せざるを得ない。本稿では、アーキテクトの視点から、CSPを用いた防御の最前線を掘り下げる。
—
1. なぜ「nonce」と「hash」が必須なのか
攻撃者は常に、ブラウザのパーサーがどう解釈するかという「境界条件」を狙っている。インラインスクリプトを許可する設定は、攻撃者にとっての「実行環境の提供」に他ならない。
現代的なCSP設計の要諦は、信頼のチェーンをいかに動的に構築するかにある。
非推奨:固定的な許可(whitelist)の罠
script-src 'self' https://trusted.cdn.com のようなドメイン単位のホワイトリストは、もはや無力だ。CDN上にホストされたライブラリ(例えば古いjQueryやAngular)の脆弱性を突かれた場合、CSPはそれを「信頼されたソースからの実行」と見なし、攻撃を阻止できない。
推奨:nonceベースの信頼チェーン
nonce(Number used once)を使用することで、サーバーサイドで生成されたユニークなトークンを持つスクリプトのみを許可する。
HTTPレスポンスヘッダーの例
Content-Security-Policy: default-src ‘self’; script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdf’ ‘strict-dynamic’;
ここで重要なのは strict-dynamic の併用だ。これを使うことで、nonceを与えられたスクリプトが動的に読み込む依存関係(子スクリプト)まで信頼チェーンを継承させることができる。これにより、レガシーなビルドパイプラインを破壊することなく、攻撃者が注入した任意のインラインスクリプトを「無効」としてパージできる。
—
2. 生成AI時代の新たな脅威:プロンプトインジェクションとCSPの接点
現在のセキュリティトレンドにおいて、LLM(大規模言語モデル)のAPIをフロントエンドから直接叩くアーキテクチャが増えている。ここで注意すべきは、生成されたテキストがDOMに挿入される際の挙動だ。
もしLLMが汚染されたデータを出力し、それが innerHTML 等を介してDOMにレンダリングされた場合、CSPがnonceを要求していても、ブラウザのパーサーが予期せぬ実行を行うリスクがある。
ガードレイルとしてのCSP
LLMからの出力には、常に Trusted Types API を組み合わせるべきだ。CSPの require-trusted-types-for 'script' ディレクティブは、文字列を直接DOMに注入するような危険なAPI(innerHTMLなど)をブラウザレベルで拒否し、ポリシーに準拠したオブジェクトのみを通すよう強制する。
// Trusted Typesの設定例
if (window.trustedTypes && trustedTypes.createPolicy) {
const policy = trustedTypes.createPolicy(‘myPolicy’, {
createHTML: (input) => DOMPurify.sanitize(input) // 信頼できるサニタイザを通す
});
// これ以降、innerHTMLへの直接代入はエラーになる
document.getElementById(‘output’).innerHTML = policy.createHTML(llmOutput);
}
—
3. 低レイヤから見た通信の整合性
CSPはブラウザのサンドボックス層で機能するが、通信プロトコルそのものが改ざんされている場合、CSPのヘッダーが正しく届かない可能性がある。HTTPヘッダーインジェクションや、中間者攻撃(MITM)によるヘッダーの剥離を想定し、インフラ層での防御を固めなければならない。
- HSTS (HTTP Strict Transport Security): CSPの設定を強制する前に、まずHTTPSの強制が不可欠だ。
- Reporting API: CSP違反が検知された際、攻撃の試行をリアルタイムで収集するエンドポイントを用意せよ。これは「防御」ではなく「脅威インテリジェンスの収集」である。
—
4. チーフホワイトハッカーとしての提言:監査の自動化
技術的な設定を導入するだけでは不十分だ。我々が構築するべきは、「誰かがうっかり unsafe-inline を書いても、CI/CDのパイプラインがそれを検知してビルドを落とす」という自動化された監査プロセスだ。
- 静的解析: ESLintやTrivyを用いて、CSP設定ファイルやメタタグを継続的にスキャンする。
- 動的解析: Playwright等の自動テストツールを用い、意図的に注入したスクリプトがCSPによってブロックされることを確認する回帰テストを実装する。
まとめ:防御の美学
CSPは、ただの「制約」ではない。それは「アプリケーションがどう振る舞うべきか」という設計思想の表明だ。
攻撃者は、常に最も抵抗の少ない道を探す。CSPによってインラインスクリプトを封じ、Trusted TypesでDOMへの不正な操作を断ち切ることは、攻撃者にとっての「スイートスポット」を消滅させることに他ならない。
泥臭い設定の積み重ねが、結果として最も堅牢な防御を構築する。最新のセキュリティトレンドを追いかけるのも良いが、まずは今動いているアプリケーションのCSPを Content-Security-Policy-Report-Only モードで運用し、何が拒否されるべきで、何が正当なトラフィックなのかを観察することから始めてほしい。
セキュリティとは、完璧を目指す旅ではない。攻撃者のコストを最大化し、彼らがターゲットを「面倒だ」と感じて去っていくのを見届ける、静かなる戦いなのだから。
コメント