CSP与安全响应头
1. CSP 概念
Content Security Policy = 浏览器执行的脚本/样式白名单。即使有 XSS 漏洞,恶意脚本也跑不起来(不在白名单)。
2. 完整指令
Content-Security-Policy: default-src 'self';
script-src 'self' 'sha256-xxx' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://ws.example.com;
frame-src 'self';
frame-ancestors 'none';
object-src 'none';
base-uri 'self';
form-action 'self';
upgrade-insecure-requests;
report-uri https://example.com/csp-report
| 指令 | 控制 |
|---|---|
| default-src | 兜底(其他没设的用这个) |
| script-src | JS |
| style-src | CSS |
| img-src | 图片 |
| font-src | 字体 |
| connect-src | fetch / XHR / WebSocket |
| frame-src | iframe 加载源 |
| frame-ancestors | 谁能 iframe 我(取代 X-Frame-Options) |
| object-src | <object> <embed> |
| base-uri | <base> 标签 |
| form-action | form action 目标 |
| upgrade-insecure-requests | 自动 http→https |
3. 源(source)值
| 值 | 含义 |
|---|---|
'self' | 当前 origin |
'none' | 不允许任何 |
'unsafe-inline' | 内联 <script> <style> |
'unsafe-eval' | eval、Function、setTimeout(string) |
'strict-dynamic' | 信任已经过 nonce 的脚本动态加载的脚本 |
'nonce-xxx' | 带特定 nonce 的内联 |
'sha256-xxx' | 哈希匹配的内联 |
https: | 任何 https |
*.example.com | 通配子域 |
data: | data URI |
blob: | blob URL |
4. 内联脚本怎么办
4.1 nonce(推荐)
<script nonce="abc123">
console.log('inline')
</script>
Content-Security-Policy: script-src 'self' 'nonce-abc123'
每次请求 nonce 不同(服务端随机生成)。SSR 友好。
4.2 hash
<script>console.log('hi')</script>
Content-Security-Policy: script-src 'self' 'sha256-...'
计算 SHA256 加白名单。静态内容用。
4.3 unsafe-inline(慎用)
打开 = CSP 大半失效。
5. report-only 模式
不强制阻止,只上报,灰度上线 CSP:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
收 1 周报告 → 调整规则 → 切到强制模式。
6. 部署步骤
- 用 report-only 收集真实违规
- 把合法第三方加白名单
- 切到 enforce
- 持续监控 violation report
7. 其他安全响应头
7.1 HSTS
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
强制 HTTPS。详见模块 02。
7.2 X-Content-Type-Options
X-Content-Type-Options: nosniff
禁止浏览器猜 MIME(防把 .txt 当 .html 解析)。
7.3 X-Frame-Options(被 CSP frame-ancestors 取代)
X-Frame-Options: SAMEORIGIN # 同源可 iframe
X-Frame-Options: DENY # 禁
新代码用 frame-ancestors。
7.4 Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
控制 Referer 头泄露。
| 值 | 含义 |
|---|---|
| no-referrer | 不发 Referer |
| origin | 只发 origin(example.com) |
| strict-origin-when-cross-origin | 同源全发,跨源只 origin(推荐默认) |
| unsafe-url | 全发(不安全) |
7.5 Permissions-Policy(前 Feature-Policy)
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()
禁用浏览器特性。interest-cohort=() 关闭 Google FLoC(隐私)。
7.6 Cross-Origin-* 三件套
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin
启用 SharedArrayBuffer、防 Spectre 跨域。详见 web.dev/why-coop-coep。
8. Nginx 配置
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-${request_id}'; ..." always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
9. 测试
securityheaders.com 评分。目标 A+。
CSP Evaluator 静态分析 CSP 配置。
10. 常见反模式
unsafe-inline+unsafe-eval全开:CSP 等于没设*当白名单:失去意义- 直接上 enforce 没 report-only:业务功能被拦
- nonce 写死字符串:失去防御意义(可预测)
- 第三方统计要 unsafe-inline 就开了:建议换支持 nonce 的版本
- HSTS preload 没准备直接申请:撤销极慢
- iframe 同源不设 frame-ancestors 限制:被恶意嵌套