这是一篇面向有实践需求的前端工程师与架构师的长文,以 CSP 的设计目标、语义、指令组合与落地策略为主线,穿插真实事故与可运行配置,帮你把 CSP 从概念变成可上线的策略。


1. CSP 是什么:浏览器可执行权限的白名单机制

CSP 全称 Content Security Policy,是浏览器支持的安全标准,站点用一个响应头把页面允许执行或加载的外部资源来源、执行方式、是否可被嵌入等规则明确告诉浏览器。浏览器据此对白名单之外的行为做拒绝或仅上报。在对抗 XSS、点击劫持与部分供应链脚本入侵方面,CSP 是最有效的客户端防线之一。MDN 的定义强调:核心是让服务器声明哪些来源可以被加载与执行,以及如何执行。(MDN Web Docs)

从标准层面看,CSP 已发展到 Level 3,引入了 strict-dynamicunsafe-hashes 等现代能力,并与 Trusted Types 协作收敛 DOM XSS 的注入面。(w3.org)


2. CSP 的基本工作流:策略声明、强制或仅上报、浏览器执行

页面返回头里带上策略:

  • 强制执行:Content-Security-Policy
  • 仅上报:Content-Security-Policy-Report-Only

仅上报模式常用于灰度或观察期,便于统计违规点并逐步收紧,而不影响线上功能。MDN 还说明若使用 report-to,需先用 Reporting-Endpoints 声明端点名称与地址。(MDN Web Docs)


3. 你需要掌握的高频指令与语义要点

为了避免策略写成看起来很安全但实际无效的样子,我们把高频指令与实战语义拆开讲。

3.1 default-src 与资源族指令

  • default-src 是兜底来源,未单独配置的资源族会继承它。
  • 常见族包括 script-srcstyle-srcimg-srcconnect-srcfont-srcframe-src 等。(MDN Web Docs)

3.2 script-src 的现代写法

  • nonce-xxxxsha256-... 哈希给内联脚本授信,杜绝 unsafe-inline
  • 配合 strict-dynamic:对带 noncehash根脚本授信,其动态创建的脚本也继承信任,同时忽略宽泛的允许列表,比如 self。这能极大简化域名白名单维护,收敛供应链风险。(MDN Web Docs)

MDN 明确指出 strict-dynamic 会把信任从带 nonce 的根脚本传递到它加载的脚本上,并在该模式下忽略传统的来源白名单。(MDN Web Docs)

3.3 frame-ancestors 与点击劫持

  • 控制谁可以把你的站点用 iframe 等嵌入。想完全禁止可设为 'none'。相较过时的 X-Frame-Options,应优先使用 frame-ancestors。(MDN Web Docs)

3.4 混合内容与迁移到 HTTPS

  • upgrade-insecure-requests 指示浏览器把同站或子资源的 HTTP 链接自动升级为 HTTPS,用于遗留链接多的场景。
  • block-all-mixed-content 已被标记为不推荐,规范层面也说明其已过时,推荐用自动升级与现代混合内容策略替代。(MDN Web Docs)

3.5 上报与可观测性

  • 现代做法使用 report-to,并通过 Reporting-Endpoints 定义端点;老的 report-uri 已在文档中标注为弃用。(MDN Web Docs)

3.6 与 Trusted Types 协作

  • 在策略中加入 require-trusted-types-for 'script',让诸如 innerHTML 这类 DOM XSS 注入点只接受 Trusted Types 值,从语言层面拒绝字符串写入。
  • 再配合 trusted-types 指令限制允许创建哪些策略。MDN 对两者有系统解释。(MDN Web Docs)

4. 真实世界的案例:Magecart 与大厂的 CSP 实践

电商场景里,Magecart 家族通过在结算页注入恶意脚本来窃取信用卡数据。British Airways 的 2018 年事件是业界常被复盘的案例:攻击者把一段特制的 JavaScript 植入航司网站与 Android 应用共享的网页组件中,造成大量交易数据被窃。安全研究给出细节分析,并指出 CSP 可以作为缓解手段之一,用以限制外联与脚本来源。(WIRED)

业界文章也分析了 BANeweggTicketmaster 等事件,指出若使用严格的 CSP(包含脚本哈希或 nonce、限制网络外联域名),可以显著降低外部篡改脚本落地与数据外泄的成功率;当然,CSP被允许域名自身被入侵的场景作用有限,这也是它作为深度防御的一部分而非唯一手段的原因。(bluetriangle.com)

另一方面,GitHub 多年前就公开其 CSP 实施经历,称其为对抗内容注入最有效的缓解之一,这说明在复杂前端体系里,CSP 依然可行且收益显著。(The GitHub Blog)


5. 从纸面策略可上线配置:三种常见落地路径

5.1 Node.js Express 中设置严格 CSP

// 注意:示例仅演示思路。真实项目应为每个请求生成随机 nonce,并注入到模板里。
// 此处不使用英文双引号。
import express from 'express';
import crypto from 'node:crypto';

const app = express();

app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64'); // 供模板内联脚本使用
  res.locals.cspNonce = nonce;

  // 使用 strict-dynamic,允许携带该 nonce 的根脚本动态加载下游脚本
  const csp = [
    `default-src 'self'`,
    `base-uri 'self'`,
    `object-src 'none'`,
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`, // 若能用哈希更好
    `img-src 'self' data:`,
    `font-src 'self'`,
    `connect-src 'self' https://api.example.com`,
    `frame-ancestors 'none'`,
    `upgrade-insecure-requests`,
    `report-to csp-endpoint`,
  ].join('; ');

  res.setHeader('Content-Security-Policy', csp);
  res.setHeader('Reporting-Endpoints', `csp-endpoint=https://report.example.com/csp`);
  next();
});

app.get('/', (req, res) => {
  // 模板里把 res.locals.cspNonce 写到内联脚本的 nonce 属性
  res.send(`
    <!doctype html>
    <meta charset=utf-8>
    <title>CSP Demo</title>
    <script nonce=${res.locals.cspNonce}>
      // 受信任的内联脚本
      console.log('csp ok');
    </script>
  `);
});

app.listen(3000);

上面示例体现了几条关键做法:用 nonce 配合 strict-dynamic 管控脚本信任链、用 frame-ancestors 防点击劫持、用 upgrade-insecure-requests 帮助遗留资源升级、用 report-to 上报违规。对应语义与可用性详见 MDN 与规范。(MDN Web Docs)

5.2 Nginx 网关侧统一下发

# server 块内
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; script-src 'self' 'nonce-$request_id' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; upgrade-insecure-requests; report-to csp-endpoint" always;

add_header Reporting-Endpoints "csp-endpoint=https://report.example.com/csp" always;

网关侧统一策略能快速覆盖多服务,但要解决 nonce 与模板的对接。可以通过上游注入变量或只允许外链脚本并给出哈希来落地。语义参考 MDN 的头字段文档。(MDN Web Docs)

5.3 静态托管场景用 meta 标签

当你用 GitHub Pages 一类静态托管,无法改响应头,可以考虑在 head 里用 meta 形式声明 CSP(注意某些指令如 report-uri 的兼容性限制)。(site)

<meta http-equiv=Content-Security-Policy content="default-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'none'">

6. 迁移与收紧的操作手册:避免一刀切造成线上事故

一套合理的节奏能让 CSP可观测演进到强制

  1. 先上 Report-Only。把当前页面真实的外联、内联习惯暴露出来,接入 report-to 收集数据。(MDN Web Docs)
  2. 清点脚本与样式来源。给确需的第三方脚本加哈希或通过受信任加载器注入 noncestrict-dynamic 可以减少域名白名单维护量。(MDN Web Docs)
  3. frame-ancestors 从宽松改为精确域或 'none',替换掉老旧的 X-Frame-Options。(MDN Web Docs)
  4. 开启 upgrade-insecure-requests,逐步清理遗留 HTTP 资源;避免使用已不推荐的 block-all-mixed-content。(MDN Web Docs)
  5. 对富交互应用,评估开启 Trusted Types 的成本与收益,在关键页面用 require-trusted-types-for 'script' 限制危险 DOM 接口的字符串输入。(MDN Web Docs)

7. 实战策略配方:从合规进攻性防御

7.1 电商结账页防数据外泄

目标是防止外部脚本加载与把数据发往未知 endpoint

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{per-request-nonce}' 'strict-dynamic';
  connect-src 'self' https://pay.example.com https://cdn.payments.example;
  img-src 'self' data:;
  style-src 'self' 'unsafe-inline';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';
  form-action 'self' https://pay.example.com;
  upgrade-insecure-requests;
  report-to csp-endpoint;

解释:用 strict-dynamic 把信任限定在携带 nonce 的根脚本及其子孙,connect-src 白名单只允许支付相关域,form-action 约束表单提交目标,frame-ancestors 禁止被套 iframe。这些组合针对 Magecart 类入侵特别有效,但若被允许的 CDN 自身被攻破,CSP 无法从根本阻止,此处需要供应链治理与前端完整性监控配合。(MDN Web Docs)

7.2 后台管理与内嵌集成

若页面需要被可信父页面内嵌(比如公司门户),可以把 frame-ancestors 指向精确的父域;若完全不需要嵌入,直接 'none'OWASP 的点击劫持备忘清单也推荐用现代指令替代 X-Frame-Options。(MDN Web Docs)

7.3 渐进使用 Trusted Types

对使用大量 innerHTML 的存量项目,可以先在只上报模式下开启:

Content-Security-Policy-Report-Only:
  require-trusted-types-for 'script';
  trusted-types default myapp#html sanitize-html;
  report-to csp-endpoint;

当违规上报趋近 0,再切换到强制模式。MDN 有关于把 Trusted TypesCSP 结合的示例与异常形态说明。(MDN Web Docs)


8. 常见坑位与避障指南

  • unsafe-inline 设在 script-src 会几乎失去 CSP 的价值。应尽量用 nonce 或脚本 sha256 哈希替代。MDNscript-src 文档详细列出各类来源表达式及其后果。(MDN Web Docs)
  • 重度依赖 eval 的框架(或启用 wasm 动态编译)可能需要 unsafe-eval 或相关豁免,但这会扩大攻击面,应通过构建与运行时配置消除。可把严格策略放在敏感页优先上线。(MDN Web Docs)
  • 混合内容治理别依赖已弃用的 block-all-mixed-content;使用 upgrade-insecure-requests 更稳妥,并在 W3C 的混合内容规范中也有相应说明。(MDN Web Docs)
  • 报告端点需要容量与清洗策略,report-to 的可用性在不同浏览器存在差异,MDN 明确其并非 Baseline,部署前要做兼容性评估。(MDN Web Docs)

9. 观念升级:把 CSP 作为深度防御合奏

OWASP 的备忘单把 CSP 定位为第二道防线:它无法阻止代码里存在漏洞,但能显著提高利用门槛。业界的安全团队通常会把它与输入校验、输出编码、模板沙箱、子资源完整性 SRI、第三方脚本监控与 WAF 联动。(OWASP Cheat Sheet Series)

与此相呼应,GitHub 的经验表明,即使在极其复杂的前端与扩展生态里,通过严格的策略、灰度与监测,CSP 依旧能带来可观的实战收益。(The GitHub Blog)


10. 一张可抄作业的生产级起步策略

下面这份策略可以作为大多数 SPA 的起点,按需替换域名与 nonce,并在 Report-Only 模式下跑一周,消化上报,再切换强制。

Content-Security-Policy:
  default-src 'self';
  base-uri 'self';
  object-src 'none';
  script-src 'self' 'nonce-{per-request-nonce}' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data:;
  font-src 'self';
  connect-src 'self' https://api.example.com https://telemetry.example.com;
  frame-src https://trusted-widgets.example.com;
  frame-ancestors 'none';
  form-action 'self';
  upgrade-insecure-requests;
  report-to csp-endpoint;
  • 如果站点需要第三方 CDN 的脚本,可给具体脚本加 sha256-... 哈希并移除 strict-dynamic,或把第三方资源走你自家的可信加载器,通过 nonce 动态注入。
  • 对于历史遗留大量内联样式的项目,style-srcunsafe-inline 在短期内很难去掉;建议引入构建期内联样式哈希化或逐步外链化,最终收紧到只允许哈希。具体迁移实践可参考 MDN 的实施文档。(MDN Web Docs)

11. CSP 报告聚合与调优闭环

  • 使用 Content-Security-Policy-Report-Onlyreport-to,把违规事件集中到日志平台,打上页面、用户代理、来源等标签,按频率×影响排序修复。
  • 若你的平台或 SaaS 已上 CSP,例如某些 Microsoft 365 组件与 SharePoint Online 正在分阶段推广 CSP 报告模式,可以参考其文档与社区讨论的实践细节。(GitHub)

12. 小结:CSP 的取舍与边界

CSP 不是银弹,却是把前端执行面收紧到你能审计的范围的关键手段。它能阻断大量直连外域未受信任脚本的攻击路径,对 Magecart 这样的供应链注入尤为关键;它也有边界,比如被允许域名本身被攻陷时需要靠供应链治理与监控兜底。和大多数工程实践一样,CSP 需要一步步把现状拉到理想模型:先观测、再收紧、最后强制。


参考与延伸阅读

  • MDN Content-Security-Policy 概览与头字段文档。(MDN Web Docs)
  • MDN script-srcstrict-dynamic、实施指南。(MDN Web Docs)
  • MDN frame-ancestors 与点击劫持对抗;老 X-Frame-Options 的替代关系。(MDN Web Docs)
  • 混合内容的新旧策略:upgrade-insecure-requestsblock-all-mixed-content 的弃用。(MDN Web Docs)
  • Trusted Typesrequire-trusted-types-for。(MDN Web Docs)
  • British Airways 事件与 Magecart 攻击复盘。(WIRED)
  • GitHubCSP 实施经验。(The GitHub Blog)
  • OWASP CSP 备忘单。(OWASP Cheat Sheet Series)