CSP内容安全策略
约 1500 字大约 5 分钟
cspsecurity
2025-08-12
概述
内容安全策略(Content Security Policy, CSP)是浏览器提供的安全层,通过声明哪些资源可以被加载和执行来有效防御 XSS、数据注入和点击劫持等攻击。CSP 是 XSS 防御体系中的最后一道关键防线。
CSP 工作原理
CSP 指令详解
核心指令
Content-Security-Policy:
default-src 'none'; # 默认禁止所有资源
script-src 'self' cdn.example.com; # JavaScript 来源
style-src 'self' 'unsafe-inline'; # CSS 来源
img-src 'self' data: https:; # 图片来源
font-src 'self' fonts.googleapis.com; # 字体来源
connect-src 'self' api.example.com wss:; # AJAX/WebSocket 目标
media-src 'self'; # 音视频来源
object-src 'none'; # object/embed 禁止
frame-src 'self'; # iframe 来源
frame-ancestors 'self'; # 允许嵌入此页面的父级
base-uri 'self'; # base 标签限制
form-action 'self'; # 表单提交目标
report-uri /csp-report; # 违规报告地址
upgrade-insecure-requests; # 自动升级 HTTP 到 HTTPS指令值说明
| 值 | 含义 | 安全性 |
|---|---|---|
'none' | 禁止所有 | 最严格 |
'self' | 仅同源 | 安全 |
https: | 仅 HTTPS | 中等 |
data: | 允许 data: URI | 需谨慎 |
'unsafe-inline' | 允许内联代码 | 不安全 |
'unsafe-eval' | 允许动态代码执行 | 危险 |
'nonce-<base64>' | 匹配 nonce | 安全 |
'sha256-<hash>' | 匹配哈希 | 安全 |
'strict-dynamic' | 信任链 | 推荐 |
Nonce 方案
Nonce(Number used Once)为每次请求生成唯一的随机值,只有包含正确 nonce 的脚本才被允许执行。
import secrets
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/')
def index():
# 每次请求生成新的 nonce
nonce = secrets.token_urlsafe(32)
html = f'''
<html>
<head>
<script nonce="{nonce}" src="/static/app.js"></script>
<script nonce="{nonce}">
// 内联脚本也需要 nonce
console.log("Authorized inline script");
</script>
</head>
<body>
<!-- 没有 nonce 的脚本会被阻止 -->
</body>
</html>
'''
response = make_response(html)
response.headers['Content-Security-Policy'] = (
f"script-src 'nonce-{nonce}' 'strict-dynamic'; "
f"style-src 'self'; "
f"default-src 'self'"
)
return responseHash 方案
对于静态的内联脚本,可以使用哈希值授权。
<!-- 计算脚本内容的 SHA-256 哈希 -->
<script>
var msg = "Hello, World";
</script># 计算哈希(注意:包括脚本标签内的所有空白)
echo -n 'var msg = "Hello, World";' | openssl dgst -sha256 -binary | openssl base64
# 输出: qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng=Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG+HiZ1guq6ZZDob/Tng='strict-dynamic
strict-dynamic 允许受信任的脚本动态加载其他脚本,大大简化了 CSP 的配置。
# 使用 strict-dynamic 时,host-based 白名单会被忽略
Content-Security-Policy: script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
# https: 和 'unsafe-inline' 作为旧浏览器的 fallback
# 支持 strict-dynamic 的浏览器会自动忽略它们报告模式
Content-Security-Policy-Report-Only
在正式部署前,使用 Report-Only 模式收集违规报告,不会实际阻止内容。
# 仅报告,不阻止
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-abc123';
report-uri /csp-report;
report-to csp-endpoint;报告接收端
from flask import Flask, request
import json
import logging
logger = logging.getLogger(__name__)
@app.route('/csp-report', methods=['POST'])
def csp_report():
report = json.loads(request.data)
violation = report.get('csp-report', {})
log_entry = {
'blocked_uri': violation.get('blocked-uri'),
'violated_directive': violation.get('violated-directive'),
'document_uri': violation.get('document-uri'),
'source_file': violation.get('source-file'),
'line_number': violation.get('line-number'),
}
# 记录到日志/监控系统
logger.warning(f"CSP Violation: {json.dumps(log_entry)}")
return '', 204Reporting API v2
# 使用新版 Reporting API
Reporting-Endpoints: csp-endpoint="https://report.example.com/csp"
Content-Security-Policy:
default-src 'self';
report-to csp-endpoint;分阶段部署策略
阶段 1:宽松策略 + Report-Only
Content-Security-Policy-Report-Only:
default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';
report-uri /csp-report;阶段 2:收紧策略 + Report-Only
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self' 'nonce-{random}' 'strict-dynamic';
style-src 'self' 'nonce-{random}';
img-src 'self' data: https:;
report-uri /csp-report;阶段 3:强制执行
Content-Security-Policy:
default-src 'none';
script-src 'nonce-{random}' 'strict-dynamic';
style-src 'self' 'nonce-{random}';
img-src 'self' data:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
report-uri /csp-report;框架集成示例
Next.js
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'nonce-{nonce}'",
"style-src 'self' 'unsafe-inline'", // Next.js 样式需要
"img-src 'self' data: blob:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
].join('; ')
}
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};Django
# settings.py
# 使用 django-csp 中间件
MIDDLEWARE = [
'csp.middleware.CSPMiddleware',
# ...
]
CSP_DEFAULT_SRC = ("'none'",)
CSP_SCRIPT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'",)
CSP_IMG_SRC = ("'self'", "data:")
CSP_FONT_SRC = ("'self'",)
CSP_CONNECT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
CSP_INCLUDE_NONCE_IN = ['script-src']
CSP_REPORT_URI = '/csp-report/'CSP 常见问题与解决
内联样式处理
# 方案 1: 使用 nonce
style-src 'nonce-{random}';
# <style nonce="{random}">...</style>
# 方案 2: 使用 hash
style-src 'sha256-xxx';
# 方案 3: 不推荐但有时必要(如第三方组件)
style-src 'unsafe-inline';第三方脚本兼容
# Google Analytics, 第三方 SDK 等
script-src 'self' 'nonce-{random}' 'strict-dynamic';
# strict-dynamic 允许受信任脚本加载的子脚本自动授信
# 无需逐一添加第三方域名最佳实践
- 从
default-src 'none'开始,逐个添加必需的指令 - 使用 nonce + strict-dynamic 替代域名白名单
- 禁止
unsafe-inline和unsafe-eval,使用 nonce 或 hash 替代 - 设置
object-src 'none',阻止 Flash 等插件 - 设置
frame-ancestors 'none',防止点击劫持 - 使用 Report-Only 模式先行观察,再逐步收紧
- 持续监控 CSP 违规报告,及时发现异常
- 结合其他安全头(HSTS, X-Content-Type-Options)形成纵深防御
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于