用内容安全策略(CSP)防御 XSS 攻击:完整指南

内容安全策略(CSP)是防御 XSS 的最有效手段之一。从 default-src 入门,到 nonce/hash 处理内联脚本、strict-dynamic、report-only 灰度上线,一篇讲透 CSP 的配置与落地策略。

最佳实践
用内容安全策略(CSP)防御 XSS 攻击:完整指南技术指南封面

内容安全策略(Content Security Policy,CSP)本质上是一张资源加载白名单:通过 HTTP 响应头告诉浏览器"本页面允许从哪些地方加载哪些资源"。它的默认逻辑是"默认拒绝"——没被列入白名单的资源,浏览器直接阻止加载。

为什么 CSP 能防住 XSS

XSS 攻击的本质是攻击者把恶意 JavaScript 注入你的页面,让它以你网站的权限运行(读 Cookie、伪造请求、篡改页面)。CSP 的防御逻辑釜底抽薪:哪怕攻击者成功把 <script src="https://evil.com/x.js"> 注入了 HTML,只要你的策略没把 evil.com 列入 script-src,浏览器就拒绝加载并记录违规。

CSP 标准历经三个版本:Level 1 建立基础防护,Level 2 引入 nonce 和 hash 处理内联脚本,现行的 Level 3 加入 strict-dynamic 等更精细的控制。所有现代浏览器均已支持。

CSP 基础:指令与来源

CSP 通过 Content-Security-Policy 响应头下发,内容是若干条"指令",每条指令管一类资源:

Content-Security-Policy: default-src 'self'; img-src *; script-src 'self' trusted.cdn.com;
  • default-src 'self':兜底规则——没单独声明的资源类型,默认只允许同源加载。
  • img-src *:图片允许任意来源(* 通配)。
  • script-src 'self' trusted.cdn.com:JS 只允许本域和指定的可信 CDN。

来源的写法:'self'(同源)、'none'(全部禁止)、具体域名、*.example.com(通配子域)、以及按 scheme(https:)。

两种模式:强制执行与仅报告

CSP 有两个响应头,对应两种模式:

模式 响应头 行为
强制执行 Content-Security-Policy 违规资源被阻止加载
仅报告 Content-Security-Policy-Report-Only 不阻止,只上报违规记录

仅报告模式是灰度落地的关键:先只收集"如果启用会拦什么",把策略调好再真正启用,避免一把上线把正常资源全拦了。

配置你的第一条 CSP

从严格到宽松逐步放行:

Content-Security-Policy: default-src 'self';

最简单也最严格——所有资源仅同源。对纯静态小站够用,但现代站点几乎都要放行一些外部资源:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://analytics.example.com;
  style-src 'self' https://fonts.googleapis.com;
  font-src 'self' https://fonts.gstatic.com;
  img-src 'self' data: https:;

Express 里加一个中间件即可下发:

app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' https://analytics.example.com; style-src 'self' https://fonts.googleapis.com;"
  );
  next();
});

常用指令速查

指令 管控对象 典型配置
default-src 所有类型的兜底 'self'
script-src JavaScript 'self' + 可信 CDN,避免 'unsafe-inline'
style-src CSS 'self' + 字体服务
img-src 图片 'self' data: https:
connect-src fetch/XHR/WebSocket 'self' + 你的 API 域
font-src 字体 'self' + 字体 CDN
object-src Flash/Java 插件 'none'(现代站点都应禁掉)
frame-ancestors 谁能 iframe 嵌你 'none' 或指定域(防点击劫持)
base-uri <base> 标签 'self'
form-action 表单提交目标 'self'

进阶:内联脚本、nonce 与 strict-dynamic

默认情况下 CSP 禁止一切内联 <script>——这正是防注入的关键,但现实项目里内联脚本常常存在。三种解法,安全性递增:

  1. 'unsafe-inline':放行所有内联脚本。方便但基本自废武功,别在生产用

  2. nonce(一次性随机值):服务器每次响应生成随机数,只放行带匹配 nonce 的脚本:

    Content-Security-Policy: script-src 'nonce-r4nd0mV4lue';
    
    <script nonce="r4nd0mV4lue">/* 可信的内联脚本 */</script>
    

    攻击者注入的脚本没有正确 nonce,被浏览器拒绝。

  3. strict-dynamic:信任"由可信脚本加载的脚本",适合现代前端(打包器动态插入脚本):

    Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic';
    

另一个实用指令是 upgrade-insecure-requests:让浏览器自动把页面里的 http:// 资源请求升级为 https://,混合内容问题一键缓解。

违规报告与监控

CSP 最有价值的副产品是违规报告:浏览器把每次违规 POST 到你指定的端点:

Content-Security-Policy: default-src 'self'; report-uri /csp-violations;

报告里包含被拦截的资源 URL、所在页面、违反的指令——它既是策略调优的输入,也是一个免费的 XSS 攻击探测器:线上突然出现的违规报告高峰,往往意味着有人正在尝试注入,或者某次发布引入了不合规资源。

落地策略:别一步到位

推荐的实施路径:

  1. 仅报告模式上线Report-Only + 宽松策略,收集一周违规数据;
  2. 按数据收紧:把合法来源加进白名单,直到"正常流量零违规";
  3. 切换为强制执行
  4. 持续监控报告:作为安全信号长期保留。

现代框架的注意点:SPA 要把 API 域名加进 connect-src;WebSocket 归 connect-src 管(wss: 来源单独声明);第三方组件(客服挂件、验证码)各要一条来源——评估第三方脚本时,CSP 白名单本身就是一份"谁在页面上跑代码"的清单。

观测云对照

CSP 违规报告本质上是浏览器端产生的安全日志。观测云 RUM 采集真实用户侧的前端数据,结合自定义上报可以把 CSP 违规与页面性能、JS 错误放在同一视图分析;违规上报端点的日志接入观测云后,可对违规量突增配置监控告警——攻击尝试或发布引入的资源问题都能第一时间感知。

常见问题(FAQ)

Q:上线 CSP 后网站部分功能挂了?
A:说明你跳过了 report-only 阶段。先切回仅报告模式,从违规报告里找出被误拦的资源补进白名单,再重新强制。

Q:CSP 能完全替代输入过滤/转义吗?
A:不能。CSP 是纵深防御的一层,不是免死金牌。输出转义、参数化查询、HttpOnly Cookie 这些基本功仍然要做。

Q:report-uri 和 report-to 用哪个?
A:report-to 是新标准(配合 Report-To 头),report-uri 老但兼容性好。过渡期两个都写。

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台