CSP (内容安全策略)
约 4 分钟阅读
最后更新: 2026-09-01
什么是 CSP (Content Security Policy)
CSP (Content Security Policy) 是一种 HTTP 响应头,允许服务器控制网页可以加载资源 (脚本、样式表、图片、字体等) 的来源。由于浏览器会阻止加载或执行 CSP 不允许的资源,因此它作为阻止通过 XSS (跨站脚本攻击) 注入的脚本执行、并阻止窃取到的信息外发的一层发挥作用。
XSS 的根本防御措施是输出转义,但实现上的遗漏无法完全消除。CSP 作为纵深防御的一层,发挥着「即使存在 XSS 漏洞,也不让攻击者的脚本执行」的作用。不过它能阻止的只是「未被允许的加载与执行」,在允许范围之内成立的攻击无法防住。在安全头中它可指定的项目最多,能收紧到什么程度就直接决定了实际效果。规范方面,Level 2 于 2016 年 12 月 15 日成为 W3C 建议标准,Level 3 截至 2026 年 8 月仍作为 W3C 工作草案在制定中。
主要指令
CSP 由多个指令组成,为每种资源类型指定允许的来源。
default-src:其他指令未明确指定的资源的默认策略。default-src 'self'仅允许来自同源的资源。回退只对加载类指令生效,base-uri、form-action、frame-ancestors即使写了default-src 'none'也仍处于未指定的状态,需要逐项单独指定script-src:控制 JavaScript 的加载来源。XSS 防御的核心指令。避免使用'unsafe-inline',推荐使用 nonce 或 hash。按照规范,一旦指定了 nonce 或 hash,'unsafe-inline'就会被忽略,因此把两者并列书写只在为不解析 nonce 的旧实现保留兼容时才有意义style-src:控制 CSS 的加载来源img-src:控制图片的加载来源connect-src:控制 fetch、XMLHttpRequest、WebSocket 的连接目标font-src:控制 Web 字体的加载来源frame-src:控制本页面的 iframe 中可以加载哪些提供方的页面 (嵌入一方的控制)frame-ancestors:方向相反,控制哪些父页面源可以将本站嵌入 iframe (被嵌入一方的控制)。它是X-Frame-Options的后继,作为点击劫持的对策要指定的正是这一项
源值的指定方式
'self':同源'none':全部阻止'nonce-{random}':仅允许具有特定 nonce 值的内联脚本/样式'strict-dynamic':也允许由通过 nonce 或 hash 许可的脚本动态加载的脚本。指定它之后,script-src中并列的域名和协议以及'self'、'unsafe-inline'在判断脚本能否加载时都会被忽略,只有 nonce 和 hash 生效- 具体域名:
https://cdn.example.com
分阶段部署步骤
CSP 的部署应循序渐进,避免破坏现有站点。
步骤 1:使用 Report-Only 模式监控
使用 Content-Security-Policy-Report-Only 头可以检测并报告策略违规,但不会阻止资源。首先使用此模式了解站点当前加载了哪些资源。
步骤 2:制定基础策略
根据报告分析结果,从图片、样式等加载类指令开始逐步收紧提供方。而对于脚本,2016 年的研究 (Lukas Weichselbaum 等「CSP Is Dead, Long Live CSP!」) 已经梳理出,留在已允许域名上的 JSONP 端点或开放重定向可能成为绕过的通道,因此设计上不能只依赖对提供方的列举 (白名单)。CSP Level 3 规范也作为参考信息提到,在 script-src 中把 nonce 或 hash 与 'strict-dynamic' 组合起来、并收紧 base-uri 的构成称为 Strict CSP,是易于实施的缓解方案。
步骤 3:生产环境部署与持续监控
切换到 Content-Security-Policy 头进行生产部署。通过 report-to 指令 (Level 3 中 report-uri 已不推荐,并给出了替换为前者的指引) 持续收集违规报告,应对误报和新的资源需求。过渡期把两者都写上,即使在只解析其中一种的环境里也不会漏掉报告。
推荐的严格策略示例
default-src 'none'; script-src 'self' 'nonce-{random}' 'strict-dynamic'; style-src 'self' 'nonce-{random}'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
此策略通过 nonce 控制内联脚本,并使用 'strict-dynamic' 允许动态加载的脚本。如果需要兼容不解析 'strict-dynamic' 的浏览器,规范的注记中给出了在 script-src 中同时写上 https: 的做法 (在能够解析它的浏览器一侧会被忽略)。
CSP 与其他安全头的协同
CSP 能够判断的只有资源是否可以加载并执行。与其他安全头组合起来,让各自补上它所负责的范围。
- 与 HSTS 配合:强制 HTTPS。以明文 HTTP 传输的页面,可能在传输途中连 CSP 头一起被删除或改写,因此要用 HTTPS 确保策略能可靠送达的传输路径
X-Content-Type-Options: nosniff:防止 MIME 类型嗅探,阻止不应被解释为脚本的资源执行- 与 CORS 的关系:两者作用的方向相反。CSP 处在限制本页面可以从哪些提供方加载资源的一侧,而 CORS 处在按照服务器的声明、仅在必要范围内打开同源策略默认关闭的响应读取的一侧
部署时费工夫的地方,是把现有的内联脚本和第三方标签逐一清点出来,改为附加 nonce 并整理提供方的写法。这项清点工作在运行年数越长的网站上量越大,因此在新项目中一开始就采用以 nonce 为前提的模板结构,之后就不必再补做针对 XSS 和点击劫持的对策。
常见误解
- 设置了 CSP 就不需要 XSS 防御措施了
- CSP 是缓解 XSS 危害的纵深防御的一层,不能替代 XSS 的根本防御措施 (输出转义)。依赖对提供方列举 (白名单) 的策略,其绕过手法已在 2016 年的研究 (Lukas Weichselbaum 等「CSP Is Dead, Long Live CSP!」) 中被梳理,CSP Level 3 规范也把使用 nonce 或 hash 与 'strict-dynamic' 的构成列为推荐形式。同时实现输出转义和 CSP 是前提。
- CSP 配置太难,小型站点不需要
- 即使是小型站点也存在 XSS 风险。从最小策略 (例如 default-src 'self'; script-src 'self') 开始,部署很容易。可以通过 Report-Only 模式确认影响,逐步收紧。