【テクニカル・上級編】Content-Security-Policy(CSP)によるスクリプト実行制限の設計 – アプリケーションセキュリティ & 安全な開発防御ガイド

「CSPは設定すれば終わり」という幻想を捨てろ:現代のWeb防御における最後の砦

多くの開発者がCSP(Content-Security-Policy)を「とりあえず入れておくべきセキュリティヘッダー」と勘違いしている。しかし、現場でインシデント対応の最前線に立つ人間から言わせれば、適切に設定されていないCSPはただの飾りであり、攻撃者にとっては「どのドメインを標的にすればいいか」という地図をわざわざ手渡しているのと同じだ。

今日は、表層的な「XSS対策」の枠を超え、ブラウザの実行エンジンと通信プロトコルの根源に迫るCSPの設計論を語る。

—

1. なぜCSPは「無効化」されるのか:攻撃者の視点

攻撃者がXSSを仕掛ける際、最初に確認するのはDOMの構造やスクリプトの脆弱性ではない。「このサイトのCSPポリシーはどうなっているか?」だ。

多くのCSPが突破される理由は、unsafe-inlineやunsafe-evalを不用意に許可しているからではない。「ホワイトリスト(ドメイン指定)の汚染」にある。例えば、CDNやGoogle Hosted Librariesを信頼ソースとして許可している場合、そこにホストされている古いライブラリの脆弱性(ガジェット)を利用した「スクリプトガジェット攻撃」で、CSPをバイパスされるのは時間の問題だ。

ドメインベースのホワイトリストは、もはや時代遅れである。今すぐ「NonceベースのCSP」へと設計思想をシフトすべきだ。

—

2. 非直感的な設計:Nonceベースの防御アーキテクチャ

Nonce(Number used once)を使用したCSPは、サーバーサイドで生成された一意のランダムなトークンを、スクリプトタグに付与することで「信頼」を証明する。これにより、攻撃者が外部から注入した悪意のあるスクリプトは、正しいNonceを持たないため実行されない。

実践的なCSPヘッダー構成例

CSPヘッダーの例:厳格な構成
Content-Security-Policy:
default-src ‘self’;
script-src ‘nonce-EDNnf03nceIOfn39fn3e9h3sdf’ ‘strict-dynamic’;
object-src ‘none’;
base-uri ‘self’;
require-trusted-types-for ‘script’;

  • strict-dynamic: これが現代の鍵だ。Nonceが付与されたスクリプトが動的に生成する子スクリプトまでを自動的に許可する。これにより、複雑な依存関係を持つモダンなフロントエンドフレームワークとの共存が可能になる。
  • require-trusted-types-for 'script': ここがプロレベルの防御だ。DOMベースのXSSを物理的に遮断する。文字列を直接DOM APIに渡すことを禁止し、明示的な型定義(Trusted Types)を通すようブラウザに強制する。

—

3. 生成AI時代のガードレイル:プロンプトインジェクションへの応用

最近のセキュリティ監査で見落とされがちなのが、LLMを統合したWebアプリケーションだ。LLMが生成したコンテンツをそのままフロントエンドでレンダリングする場合、それは潜在的なDOM XSSの温床となる。

ここでCSPが果たすべき役割は、「LLMからのアウトプットに対する隔離(Sandboxing)」だ。

// サーバーサイドでのNonce生成と埋め込み(Node.js/Expressの例)
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(16).toString(‘base64’);
res.setHeader(‘Content-Security-Policy’, script-src 'nonce-${res.locals.nonce}' 'strict-dynamic';);
next();
});

// フロントエンド側:LLMの応答をDOMに挿入する際はTrusted Typesを介する
const policy = trustedTypes.createPolicy(‘default’, {
createHTML: (input) => DOMPurify.sanitize(input) // 洗浄を強制
});
document.getElementById(‘ai-output’).innerHTML = policy.createHTML(aiResponse);

このように、CSPとTrusted Typesを組み合わせることで、LLMが意図せず生成した悪意あるスクリプトや、プロンプトインジェクションによるDOM操作を、ランタイムレベルで無効化できる。

—

4. チーフホワイトハッカーとしての提言:監査の極意

CSPを導入しただけで満足するな。以下の手順で継続的に検証し、チューニングを行え。

1. Report-Onlyモードで開始せよ: 本番環境にいきなり適用してはならない。まずは Content-Security-Policy-Report-Only ヘッダーを用いて、どのスクリプトがブロックされるか(あるいは許可されるべきか)をレポートエンドポイントに送信させ、少なくとも2週間はログを分析しろ。
2. サードパーティスクリプトの棚卸し: 「利便性」のために読み込んでいるトラッキングスクリプトが、あなたのアプリケーションの安全性を損ねていないか。CSPレポートを分析すれば、攻撃者の踏み台になり得る「死んだライブラリ」が即座に炙り出せるはずだ。
3. パケット構造の解析: たまにはChromeのDevToolsだけでなく、tcpdumpやWiresharkを開き、TLSハンドシェイクの裏側でどのようなヘッダーがやり取りされているかを確認しろ。CSPが正しく適用されていることを、ネットワークレベルのパケットで見抜くのがプロの仕事だ。

セキュリティとは、終わりのないチェスのようなものだ。CSPは、そのチェス盤上で攻撃者の動ける範囲を極限まで狭めるための「空間支配」に他ならない。

技術に逃げ道はない。細部(低レイヤのメモリ挙動やブラウザの仕様)に宿る神を信じ、設定の一つひとつに合理的な根拠を持たせろ。それが、あなたの書くコードを最強の盾に変える唯一の道だ。

コメント

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