CSP 内容安全策略
CSP 通过响应头控制页面可加载的资源来源,是防御 XSS 最重要的安全机制之一。
[!info] related notes
- 所属 MOC: security-moc
- 总览: http-response-headers
- 防御目标: XSS 跨站脚本攻击
- 并列安全头: HSTS, x-content-type-options, x-frame-options, referrer-policy, permissions-policy
CSP 内容安全策略
一句话定义
CSP(Content-Security-Policy)通过 HTTP 响应头告诉浏览器:这个页面只能从哪些来源加载脚本、样式、图片、字体、iframe 等资源,从而防御 XSS 和数据注入攻击。
它要解决什么问题
XSS 的本质是攻击者把恶意脚本注入页面并执行。CSP 的思路是:即使页面被注入了脚本,只要脚本来源不在白名单里,浏览器就拒绝执行。
核心机制
服务器在响应中声明:
Content-Security-Policy: script-src 'self'
浏览器收到后,对页面加载的所有脚本做来源检查。不在白名单内的脚本会被拦截,并在控制台报错:
Refused to load the script 'https://evil.com/steal.js'
because it violates the following Content Security Policy directive: "script-src 'self'"
指令速查
| 指令 | 控制内容 |
|---|---|
default-src | 所有资源的默认来源(兜底) |
script-src | JS 来源 |
style-src | CSS 来源 |
img-src | 图片来源 |
font-src | 字体来源 |
connect-src | fetch、XHR、WebSocket、EventSource 连接目标 |
frame-src | iframe 加载目标 |
frame-ancestors | 谁可以嵌入我(替代 X-Frame-Options) |
object-src | object / embed / applet |
base-uri | <base> 标签的 URL |
form-action | 表单提交目标 |
upgrade-insecure-requests | 自动升级 HTTP 子资源到 HTTPS |
report-uri / report-to | 违规报告发送地址 |
指令继承规则:如果某个指令没有显式设置,它会回退到 default-src 的值。比如没设 img-src,就用 default-src 的值。
基础安全模板
最小可用
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
含义:
- 所有资源默认只能从同源加载
- 禁止 object/embed/applet
<base>标签只能指向同源- 不允许任何页面嵌入我
严格脚本策略
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-random123';
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
'nonce-random123' 表示只有带对应 nonce 属性的 <script> 标签才能执行:
<script nonce="random123">
// 允许执行
</script>
<script>
// 被 CSP 拦截
</script>
nonce vs hash
| 方式 | 写法 | 特点 |
|---|---|---|
| nonce | 'nonce-abc123' | 每次请求动态生成,需要后端配合 |
| hash | 'sha256-xyz...' | 对脚本内容算 hash,不需要动态生成 |
nonce 方式
Content-Security-Policy: script-src 'nonce-abc123'
<script nonce="abc123">
console.log("allowed");
</script>
hash 方式
Content-Security-Policy: script-src 'sha256-BpfBw7ivV8Q0su0895asd...'
<script>
console.log("allowed"); // hash 匹配这段内容
</script>
Report-Only 模式
只报告,不拦截。适合上线前观察。
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
用途:
- 观察哪些资源会被 CSP 拦截
- 发现还在使用的第三方脚本
- 找出需要改造的 inline script
推荐上线流程
1. 先用 Content-Security-Policy-Report-Only 观察
2. 收集报告,修复第三方资源和 inline script
3. 切换到 Content-Security-Policy 正式拦截
这是更稳的上线方式,直接上 Enforcement 可能导致线上资源加载失败。
常见坑
unsafe-inline
script-src 'self' 'unsafe-inline'
允许所有 inline script 执行。这会大幅削弱 CSP 对 XSS 的防护,因为 XSS 注入的通常就是 inline script。
unsafe-eval
script-src 'self' 'unsafe-eval'
允许 eval()、new Function() 等动态代码执行。同样削弱安全性。
同时使用 unsafe-inline 和 nonce
script-src 'self' 'unsafe-inline' 'nonce-abc123'
在 CSP Level 2 中,如果同时有 'unsafe-inline' 和 nonce/hash,'unsafe-inline' 会被忽略。但在 CSP Level 1 中不会,所以不要依赖这个行为。
connect-src 忘记配
如果页面用 fetch 或 WebSocket 连接外部 API,但 CSP 没设 connect-src,它会回退到 default-src。如果 default-src 是 'self',外部 API 请求会被拦截。
# 需要明确允许 API 域
connect-src 'self' https://api.example.com
frame-ancestors 替代 X-Frame-Options
CSP 的 frame-ancestors 比 X-Frame-Options 更灵活:
# X-Frame-Options 只能 DENY 或 SAMEORIGIN
X-Frame-Options: SAMEORIGIN
# CSP frame-ancestors 可以指定具体来源
Content-Security-Policy: frame-ancestors 'self' https://partner.example.com
与 XSS 防御的关系
CSP 不能替代输入过滤和输出编码。它是纵深防御的一层:
| 手段 | 防什么 |
|---|---|
| 输入过滤 / 输出编码 | 防止恶意内容进入页面 |
| CSP | 即使内容进入页面,也阻止脚本执行 |
| HttpOnly Cookie | 即使 XSS 发生,也阻止脚本读取 Cookie |
三者互补,不能互相替代。
边界与易混淆点
- CSP 不是万能的:它不能防御所有 XSS 变种(如 DOM XSS 如果用的是白名单内的脚本)。
- CSP Level 2 vs Level 3:浏览器支持程度不同,
strict-dynamic等高级指令需要 Level 3。 - inline style 也需要管:
style-src 'unsafe-inline'允许内联样式,虽然风险低于 script,但仍可能被用于 CSS 注入。 - 违反 CSP 的报告可以收集分析:通过
report-uri或report-to指令发送到后端,用于发现潜在攻击和调试策略配置。