跳轉到

2.4 CSP 與瀏覽器安全標頭#

思路轉換:從「擋輸入」到「限權限」#

2.1–2.3 的防禦都在「阻止惡意資料進來」。但縱深防禦要問:若注入成功了,瀏覽器還能做什麼?

CSP(Content Security Policy,2012 W3C 標準)的回答:由伺服器聲明「這頁允許做什麼」,瀏覽器執行白名單。這是作業系統「沙盒/最小權限」思想在瀏覽器的移植。

CSP 的語法:資源白名單#

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://img.example.com;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
  upgrade-insecure-requests;
指令 管什麼
default-src 所有資源的預設
script-src 最重要:腳本來源
style-src 樣式
img-src / font-src 圖片/字型
connect-src fetch/XHR/WebSocket 目標
frame-ancestors 誰能嵌我(2.3 clickjacking)
report-uri 違規上報

嚴格 CSP 下,2.1 的 <script>alert(1)</script> 注入無效 —— 瀏覽器看到來源不在白名單直接拒載。

nonce 模式:現代 CSP 的正解#

舊式 CSP 依賴 unsafe-inline(允許內聯腳本)—— 那等於對 XSS 繳械。現代做法:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m-per-request' 'strict-dynamic';
<script nonce="r4nd0m-per-request" src="/app.js"></script>
  • nonce 每請求隨機:攻擊者注入的腳本沒有正確 nonce → 拒執行
  • strict-dynamic:被信任腳本動態載入的腳本自動信任(支援 webpack 分包)
// Axum 中介層:每請求生成 nonce 並注入標頭
pub async fn csp(mut req: Request, next: Next) -> Response {
    let nonce = rand_nonce();
    let mut res = next.run(req).await;
    res.headers_mut().insert(
        "content-security-policy",
        format!("script-src 'self' 'nonce-{nonce}' 'strict-dynamic'").parse().unwrap(),
    );
    res
}

report-only 模式:安全上線的節奏#

Content-Security-Policy-Report-Only: ...; report-uri /csp-report

先跑 Report-Only:只上報違規不阻止 —— 觀察真實流量需要哪些資源,逐步收緊,再切正式模式。部署安全控制的通用節奏:

\[ \text{觀測} \to \text{收緊} \to \text{強制} \]

五個必設標頭總表#

標頭 防 值範例
Content-Security-Policy XSS、clickjacking nonce 模式
Strict-Transport-Security 降級攻擊 max-age=31536000; includeSubDomains; preload
X-Frame-Options clickjacking(舊) DENY
X-Content-Type-Options MIME 嗅探攻擊 nosniff
Referrer-Policy URL 洩漏(含 token) strict-origin-when-cross-origin

MIME 嗅探攻擊:nosniff 在防什麼#

瀏覽器歷史上會「猜」檔案類型:上傳的 profile.jpg 內容其實是 HTML/script,被當網頁打開就執行。nosniff 令瀏覽器嚴格按 Content-Type 處理 —— 又一個「關閉聰明但危險的預設行為」。

Referrer-Policy 在防什麼#

https://example.com/reset?token=abc123 → 點外部連結 → evil.com 看到 Referer 全 URL

重設 token 在 URL 裡會隨 Referer 外洩。strict-origin-when-cross-origin 跨站只送域名 —— URL 含機密的反模式因此被部分救回(但正解仍是:機密不進 URL,2.2 的 GET 語意教訓)。

檢測工具#

curl -sI https://example.com | grep -iE 'content-security|x-frame|strict-transport'
# 或用 securityheaders.com 打分

習題#

  1. 把你的站以 report-only 起步收緊到 nonce CSP,記錄過程中被擋掉的資源。
  2. unsafe-inline 為什麼讓 CSP 失效?strict-dynamic 解決了什麼遷移問題?
  3. 解釋 MIME 嗅探攻擊的完整鏈路:上傳 → 儲存 → 訪問路徑 → 執行。