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