CSP 内容安全策略

CSP 通过响应头控制页面可加载的资源来源,是防御 XSS 最重要的安全机制之一。

#type / concept #status / evergreen #tech / security #resource / http #platform / browser

[!info] related notes

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-srcJS 来源
style-srcCSS 来源
img-src图片来源
font-src字体来源
connect-srcfetch、XHR、WebSocket、EventSource 连接目标
frame-srciframe 加载目标
frame-ancestors谁可以嵌入我(替代 X-Frame-Options)
object-srcobject / 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-ancestorsX-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-urireport-to 指令发送到后端,用于发现潜在攻击和调试策略配置。
创建于 2026/5/24 更新于 2026/5/27