本文 AI 產出,尚未審核

CSP(內容安全政策)

簡介

在現代 Web 應用中,跨站腳本(XSS) 是最常見且危害最大的攻擊手法之一。攻擊者往往藉由注入惡意 JavaScript,竊取使用者的認證資訊、操控頁面行為,甚至發動釣魚攻擊。傳統的防禦方式(如輸入過濾或轉義)雖然必要,但往往難以保證 100% 完整,且維護成本高。

內容安全政策(Content Security Policy,簡稱 CSP) 是瀏覽器層面的防護機制,它允許網站管理者以白名單的方式明確規範哪些資源可以被載入、執行。只要瀏覽器遵守 CSP,任何未被授權的腳本、樣式、圖片或框架都會被自動阻擋,從根本上降低 XSS 與資料外洩的風險。
本篇文章將從概念、指令、實作範例、常見陷阱與最佳實踐,帶你一步步掌握 CSP,讓你的前端專案在安全性上更上一層樓。


核心概念

1. CSP 的運作原理

CSP 透過 HTTP 回應標頭 Content‑Security‑Policy(或 <meta http‑equiv="Content‑Security‑Policy">)向瀏覽器宣告「允許的資源來源」。瀏覽器在解析 HTML、CSS、JavaScript 時,都會比對這份白名單;一旦發現違規的載入行為,就會拋出 CSP violation,並根據設定決定是否阻止該資源。

重點:CSP 不是「防止」資源被寫入,而是「限制」資源的 來源執行方式

2. 常見指令(Directives)

指令 說明 常見值
default-src 所有未明確指定的資源類型的預設來源 'self'https:
script-src JavaScript 來源 'self'https://cdn.example.com'nonce-xxxx''sha256-xxxx'
style-src CSS 來源 'self''unsafe-inline'(不建議)
img-src 圖片來源 'self'data:https://images.example.com
connect-src XHR、WebSocket、EventSource 等連線來源 'self'https://api.example.com
font-src 字型檔來源 'self'https://fonts.gstatic.com
frame-src <iframe><frame> 的來源 https://www.youtube.com
object-src <object>、<embed>、<applet> 的來源 none(建議禁用)
base-uri <base> 標籤的允許來源 'self'
report-uri / report-to CSP 違規報告的接收端點 https://csp-report.example.com/

小技巧:如果你只想針對某一類資源作限制,其他資源就使用 default-src 作為後備。

3. noncehash:允許內嵌腳本的安全方式

3.1 為什麼不直接使用 unsafe-inline

unsafe-inline 會允許所有內嵌腳本(<script>onclick 等)執行,等於把 CSP 的防護關掉。在正式環境絕不要使用

3.2 使用 nonce(一次性隨機字串)

伺服器在每次回應時產生一個隨機字串(如 nonce-abc123),在 CSP 標頭裡宣告,並把相同的 nonce 加到允許執行的 <script> 標籤上。只有帶有正確 nonce 的腳本會被執行。

// 伺服器端 (Node.js + Express)
app.use((req, res, next) => {
  const crypto = require('crypto');
  const nonce = crypto.randomBytes(16).toString('base64'); // 產生 22 字元的 base64

  // 把 nonce 存到 res.locals,稍後渲染模板時可使用
  res.locals.cspNonce = nonce;

  // 設定 CSP 標頭
  res.setHeader(
    'Content-Security-Policy',
    `script-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self'`
  );
  next();
});
<!-- HTML 模板 (ejs) -->
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <title>使用 nonce 的 CSP 範例</title>
</head>
<body>
  <h1>Hello CSP</h1>

  <!-- 只有帶有正確 nonce 的腳本會被執行 -->
  <script nonce="<%= cspNonce %>">
    console.log('這段腳本被允許執行');
  </script>
</body>
</html>

3.3 使用 hash(內容雜湊)

如果腳本內容是固定不變的,可以直接把腳本的 SHA256(或 SHA384、SHA512)雜湊寫入 CSP。瀏覽器會比對腳本內容的雜湊值,只有匹配的腳本會被允許。

<!DOCTYPE html>
<html lang="zh-Hant">
<head>
  <meta charset="UTF-8">
  <title>使用 hash 的 CSP 範例</title>
  <!-- 計算後的 SHA256 雜湊值 -->
  <meta http-equiv="Content-Security-Policy"
        content="script-src 'self' 'sha256-3vJrV+5t1YQ0kB1V4KZ0Xc5K2XkKZx6X5p9wX8Q7vZc=';">
</head>
<body>
  <script>
    // 這段腳本的內容必須與上面的 hash 完全相同
    console.log('hash 方式允許的腳本');
  </script>
</body>
</html>

備註:若腳本內容稍有變動(例如空白或註解),hash 就會失效,需要重新計算。

4. 報告機制:report-uri vs report-to

當 CSP 阻擋了資源或偵測到違規時,瀏覽器會將違規資訊 POST 給你指定的端點。report-uri 是較早的規範,report-to 則是新一代、支援更豐富的報告格式。

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.jsdelivr.net;
  report-to csp-endpoint

Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://csp-report.example.com/collect"}],"include_subdomains":true}

在伺服器端,你可以簡單寫一個接收報告的 API,收集違規資訊作為安全審計的依據。

// Express 接收 CSP 報告
app.post('/csp-report', express.json({ type: ['application/csp-report', 'application/json'] }), (req, res) => {
  console.log('收到 CSP 報告:', req.body);
  // TODO: 儲存至資料庫或發送告警
  res.status(204).end(); // 回應空白表示已收到
});

常見陷阱與最佳實踐

陷阱 說明 解決方案
使用 unsafe-inline 允許任意內嵌腳本,等同關閉 CSP。 改用 noncehash,或將腳本抽離至外部檔案。
忘記 script-src 包含 'self' 若只寫了 CDN,自己網站的腳本會被阻擋。 script-src 中同時加入 'self'
style-src 漏寫 unsafe-inline 許多 UI 框架使用內嵌樣式,若未允許會導致樣式失效。 使用 nonce/hash 允許必要的內嵌樣式,或改為外部 CSS。
報告端點未正確設定 CSP 違規不會被記錄,失去偵測機會。 同時設定 report-uri(舊版)與 report-to(新版),確保端點可接受 application/csp-report
CSP 與第三方服務衝突 CDN、分析工具、廣告等常有額外資源需求。 逐一列出所需來源,或使用子域名隔離第三方腳本。
忘記更新 hash 稍微改動腳本就會導致 CSP 錯誤。 若腳本頻繁變動,改用 nonce;若固定,寫自動化腳本產生 hash。

最佳實踐清單

  1. default-src 開始:先設 default-src 'self',確保所有未明確允許的資源都被封鎖。
  2. 逐步放寬:根據實際需求,僅在必要的指令上加入額外來源(如 script-src https://cdn.jsdelivr.net)。
  3. 使用 noncehash:盡量避免 unsafe-inline
  4. 啟用報告:在開發環境先使用 report-onlyContent-Security-Policy-Report-Only)觀察違規,再正式上線改為強制模式。
  5. 自動化測試:將 CSP 產生與驗證納入 CI pipeline,確保每次部署不會破壞政策。
  6. 結合 Helmet(Node.js)或 Django‑csp 等套件:減少手動寫標頭的錯誤機會。
// 使用 Helmet 自動產生 CSP(Node.js 範例)
const helmet = require('helmet');
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdn.jsdelivr.net", (req, res) => `'nonce-${res.locals.cspNonce}'`],
      styleSrc: ["'self'", "'unsafe-inline'"], // 若必須允許內嵌樣式
      imgSrc: ["'self'", "data:", "https://images.example.com"],
      reportUri: '/csp-report',
    },
  })
);

實際應用場景

1. 電子商務平台

在購物車、結帳流程中,若被注入惡意腳本,攻擊者可以竊取信用卡資訊或改寫付款金額。透過 CSP,僅允許自家域名與受信任的支付金流 CDN,所有第三方腳本(如廣告)若未列入白名單即被阻擋,大幅降低風險。

2. 企業內部管理系統(Intranet)

企業系統常使用大量的 AJAX 與 WebSocket 互動。使用 connect-src 明確限制只允許公司內部 API(https://api.company.com)與特定的即時通訊服務,防止惡意外部站點利用 XSS 發起跨站請求(CSRF + XSS)。

3. 單頁應用(SPA)

React、Vue、Angular 等框架會在客戶端大量產生內嵌腳本(如動態生成的 style 標籤)。此時可在建置流程中自動為所有產生的腳本加上 nonce,或使用框架提供的 CSP 插件(如 react-helmet)統一管理。

// React 使用 react-helmet 設定 CSP
import { Helmet } from 'react-helmet';

function App() {
  const nonce = crypto.randomUUID(); // 假設在服務端產生並注入
  return (
    <>
      <Helmet>
        <meta
          httpEquiv="Content-Security-Policy"
          content={`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'`}
        />
      </Helmet>
      <h1>SPA with CSP</h1>
      <script nonce={nonce}>console.log('SPA script');</script>
    </>
  );
}

4. 多租戶 SaaS 平台

每個租戶可能會自行上傳自訂的 HTML 小工具。平台可以為每個租戶產生獨立的 nonce,只允許該租戶的腳本執行,避免租戶之間的腳本互相干擾或攻擊。


總結

  • CSP 是瀏覽器層面的白名單機制,能有效阻止未授權的腳本、樣式與其他資源執行。
  • 透過 default-srcscript-srcstyle-src 等指令,配合 noncehash 取代危險的 unsafe-inline,即可在不犧牲功能的前提下提升安全性。
  • 報告機制report-uri / report-to)讓開發者在上線前先以 Report‑Only 模式觀測違規,確保政策不會誤殺正當資源。
  • 常見的陷阱包括忘記加入 'self'、濫用 unsafe-inline、以及未正確設定報告端點;遵循 「最小權限」「逐步放寬」 的原則,可大幅降低錯誤配置的風險。
  • 在實務上,從 電商、內部系統、SPA 到多租戶平台,CSP 都是提升前端安全的必備工具;搭配自動化建置、Helmet、react‑helmet 等套件,可讓政策的維護變得輕鬆且一致。

透過本文的概念說明與實作範例,你已掌握在 JavaScript 專案中導入 CSP 的全流程。立即在開發環境測試、調整政策,然後在生產環境啟用,讓你的網站在面對 XSS 攻擊時,擁有堅實的防護屏障。祝開發順利,安全無憂!