资讯文章 资讯 · NEWS
CSP 别一上来就强制执行:目录站从 Report-Only 走到强制的六周
2026-10-24 约 1,782 字 预计阅读 4 分钟 yiyunkj
C
Content-Security-Policy 的作用是告诉浏览器哪些资源允许执行。它防不住注入本身,但能让注入的脚本跑不起来——这是第二道防线,不是第一道。第一道仍然是输出转义和输入过滤。
它的问题也在这里:策略写严了,正常脚本一起被挡。所以几乎所有落地指南都指向同一个顺序——先用 Content-Security-Policy-Report-Only 跑一段时间,收集违规报告,处理完再切强制。

图片来源:网络,版权归原作者所有,侵权请联系删除
第 1 周:定起点策略
起点不要从一长串域名白名单开始,那种策略既难维护又容易被绕过。从这一条开始:
Content-Security-Policy-Report-Only:
script-src 'nonce-随机值';
object-src 'none';
base-uri 'none';
report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"
两个细节。第一,nonce 必须每次响应都是新的随机值,用服务端模板注入;进程启动时生成一次然后复用的 nonce 等同于静态值,攻击者看一次就永久绕过。第二,仅报告模式不能用 meta 元素下发,必须是 HTTP 响应头。
第 2 周:把上报端点建起来
上报有两代机制。report-uri 是旧的、支持面最广;report-to 基于 Reporting API,是现在的标准做法。MDN 的建议是两个都写,直到 report-to 覆盖到你关心的浏览器范围。
报告以 JSON 形式 POST 过来,类型是 csp-violation。真正有用的字段只有几个:被阻断的地址、生效的指令、文档地址、源文件与行号、以及一小段违规代码样本。按「指令 + 被阻断地址」分组统计,不要按原始条数看——同一个问题会刷出成千上万条。
第 3 到 4 周:分三堆处理
| 类别 | 判断依据 | 处理办法 |
|---|---|---|
| 自己的内联代码 | 源文件在自己域名下 | 移到外部文件,或加 nonce |
| 已知第三方脚本 | 能说出是谁加的、干什么用 | nonce + strict-dynamic,或白名单条目 |
| 不认识的来源 | 没人能解释它为什么在页面上 | 当成可疑,先查再动 |
第三堆才是 CSP 真正值钱的地方。一个没人认领的脚本,往往意味着有人往页面里塞了东西却没走流程。
关于 strict-dynamic:一旦在同一条指令里写了它,现代浏览器会忽略该指令中的主机白名单,只认 nonce 或 hash。白名单留着只是给不认识 strict-dynamic 的旧浏览器兜底。所以第三方脚本必须由带 nonce 的脚本去加载,靠自己在 HTML 里写死域名是无效的。
第 5 周:处理噪声
报告里有一类干扰源:浏览器扩展往页面里注入脚本。这不是你的问题,也不用为它改策略,识别出特征后在统计里排除掉即可。
另一类是目录站特有的——用户提交内容里带的富文本。这类内容如果允许内联事件属性,CSP 会大量报错。正确做法是入库前清洗、输出时转义,而不是给 CSP 开 unsafe-inline。
第 6 周:切强制,然后继续观察
报告安静一段时间(覆盖一个完整业务周期,包括月末、促销这类非常规流量)之后,把同一份策略复制到强制执行头。
切换之后两件事别停:一是保留一个 report-only 副本,用来试下一次的收紧策略——两个头可以并存,一个阻断、一个只报告;二是上报端点要一直开着,强制执行之后新出现的违规,通常意味着新脚本上线或者有人在尝试注入。
六个常见错误
- script-src 里同时出现 unsafe-inline 和 unsafe-eval——这等于把 CSP 的 XSS 防护关掉了。
- nonce 写死在模板或配置里。
- 用通配域名,比如 https://*.某公有云域名,攻击者在同一个云服务上托管脚本就能绕过。
- 只锁 script-src,忘了 connect-src。脚本执行被挡住了,数据外发通道还开着。
- 不设 object-src 和 base-uri。这两个几乎是白送的。base 标签注入是很实际的 XSS 入口。
- 上线之后不再复查。每接入一个外部资源,策略就该跟着走一遍。
验证
打开 DevTools,控制台会逐条列出被阻断的资源和触发的指令。再走一遍关键流程:登录、提交表单、支付或跳转、错误状态页——很多违规只在特定操作后才出现。
最后一句提醒:CSP 不能替代输入过滤。它是纵深防御里的一层,不是唯一一层。