CSP 与 SRI 必须配合使用才能有效防御 CDN 劫持和脚本注入:CSP 限制资源加载来源但不校验内容,SRI 校验外链资源完整性但无法防护内联脚本;二者缺一不可,需同时配置 script-src 白名单/nonce 与 integrity+crossorigin 属性。

CSP 和 SRI 一起用,才能真正拦住 CDN 劫持和脚本注入——单靠一个,都有明确绕过路径。
为什么只配 CSP 不足以防 CDN 劫持
CSP 的 script-src 允许你写 https://cdn.jsdelivr.net,但这个只是“域名白名单”。如果 CDN 被中间人污染、或 CDN 自身被攻陷(比如上游构建流水线遭入侵),浏览器照样会加载并执行那个被篡改的 JS 文件——因为域名没变,CSP 认为它“合法”。
常见错误现象:Refused to load script from 'https://cdn.jsdelivr.net/xxx.js' because it violates the following Content Security Policy directive 这类报错根本不会出现,脚本照常执行,用户已中招。
- CSP 只校验“来源”,不校验“内容”
- 攻击者只要控制 CDN 响应体(哪怕只改一行),就能注入恶意逻辑
-
unsafe-inline、unsafe-eval这类宽松指令会让风险指数级放大
为什么只加 SRI 不足以防内联脚本注入
SRI 只作用于带 integrity 属性的外链资源,对 <script>alert(1)</script>、javascript:...、onerror=... 完全无效。攻击者只要把 payload 塞进页面 HTML 里(比如富文本渲染、服务端模板拼接),SRI 就彻底失能。
使用场景:用户上传的 Markdown 渲染成 HTML 后直接 innerHTML 插入;后端用 fmt.Sprintf 拼接响应;模板引擎误用 template.HTML 强制信任未过滤内容。
- SRI 是“资源完整性守门员”,不是“执行权限守门员”
- 它不阻止内联脚本、
eval()、setTimeout("...")等动态执行方式 - 缺少
crossorigin="anonymous"会导致 SRI 静默失效(浏览器拒绝比对哈希)
必须同时启用的最小可行配置
两者配合的核心逻辑是:CSP 切断所有非法执行入口,SRI 确保合法入口里的内容未被篡改。缺一不可。
关键参数差异:
- CSP 头必须包含
script-src 'self' https://cdn.jsdelivr.net+'strict-dynamic'(如需支持动态加载)或显式 nonce:script-src 'self' 'nonce-abc123' - 每个外链脚本必须带
integrity和crossorigin="anonymous",例如:<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js" integrity="sha256-..." crossorigin="anonymous"></script> - 禁用所有高危行为:
script-src里不能有*、data:、unsafe-inline、unsafe-eval
性能影响极小:SRI 是一次哈希校验,CSP 是浏览器原生策略匹配,无运行时开销。兼容性上,integrity 在 Chrome 45+/Firefox 43+/Safari 10.1+ 全面支持;'strict-dynamic' 需要 CSP Level 3,但 nonce 方案在所有现代浏览器都稳定可用。
容易踩的坑:nonce 生成与复用
Go/Node.js/PHP 等后端生成 nonce 时,最常见错误是“一次生成、全局复用”。浏览器会拒绝执行所有共享同一 nonce 的脚本,导致页面 JS 全部失效。
正确做法:
- 每次 HTTP 响应都调用加密安全随机数生成器(如 Go 的
crypto/rand.Read())产生新 nonce - 把这个 nonce 同时注入到响应头
Content-Security-Policy: script-src 'nonce-xxx'和对应<script nonce="xxx">标签中 - 绝对不要缓存含 nonce 的 HTML 页面,或在 CDN 上缓存带固定 nonce 的响应
复杂点在于:如果你用 SSR 框架(如 Next.js、Nuxt),需要确保服务端渲染阶段生成的 nonce 和最终发送的 CSP 头完全一致——前后端不一致会导致脚本被静默丢弃,且控制台可能不报错。

















