网站内容安全策略:CSP的配置、绕过风险与部署

CSP(内容安全策略)是一道响应头,告诉浏览器哪些脚本能跑、哪些域名能加载资源。部署节奏必须分三步:先Report-Only跑14天收集违规源,再把白名单收敛干净,

CSP部署:报告模式先跑两周再切强制CSP: report-only for two weeks, then enforce 一上来就enforce会把站内第三方脚本全打挂Enforcing on day one breaks every third-party script 1 Report-Only1 Report-Only 只收集违规 不拦截Collect violations, no block 跑14天 收集所有来源Run 14 days to collect sources 2 白名单收敛2 Whitelist tuning 按报告加域名 不violent-inlineAdd domains from reports sha256 hash 替代unsafe-inlinesha256 instead of unsafe-inline 3 Enforce3 Enforce 违规日志为0再切Switch when reports hit 0 保留report-uri 继续监控Keep report-uri watching 核心指令Core directives default-src 兜底 script-src 控脚本default-src fallback, script-src controls JS object-src ‘none’ frame-ancestors 防嵌套object-src none, frame-ancestors 红线Red lines 别开 * 通配符 等于没配Wildcard * equals no CSP unsafe-inline 留着等于被绕过unsafe-inline defeats the purpose 判定:报告模式跑满14天且违规数降到个位数,再切EnforceDecision: enforce after 14 days of near-zero reports 反直觉:CSP不是越严越安全,配错了比不配更危险Counterintuitive: misconfigured CSP is worse than none 先报告后强制,把违规源一个个收进白名单Report first, enforce later
CSP三步部署:Report-Only收集、白名单收敛、Enforce强制

网站内容安全策略:CSP的配置、绕过风险与部署

结论先行:CSP(内容安全策略)是一道响应头,告诉浏览器哪些脚本能跑、哪些域名能加载资源。部署节奏必须分三步:先Report-Only跑14天收集违规源,再把白名单收敛干净,违规日志降到个位数后才切Enforce。一个反直觉判断是,配错的CSP比不配更危险——开了unsafe-inline等于这道墙白砌。

一、CSP到底挡住什么

CSP通过HTTP响应头下发规则,浏览器据此决定哪些资源能加载。它主要防两类问题:XSS注入的恶意脚本、未经授权的第三方域被嵌进页面。

它不是防火墙。CSP不能挡SQL注入,也不能挡服务器被入侵,它管的是浏览器侧的资源加载。和HTTPS、HSTS是配合关系,HTTPS配置方法见独立站HTTPS配置。

独立站常见的痛点是第三方脚本太多:GA4、GTM、Hotjar、在线客服、广告像素。每加一个脚本,CSP白名单就要加一条域名,否则脚本被浏览器拦掉。第三方脚本治理见独立站第三方脚本治理。

一个常被忽略的事实是,CSP对SEO几乎没有负面影响。Googlebot本身不执行JS之外的资源限制,配好CSP不会影响收录。担心CSP影响抓取是多余的。

指令 管什么 建议值
default-src 所有资源兜底 ‘self’
script-src JS脚本来源 ‘self’ + 统计域名
style-src CSS来源 ‘self’ ‘unsafe-inline’
img-src 图片来源 ‘self’ data: + CDN
object-src 插件 ‘none’(必关)
frame-ancestors 谁能嵌套本站 ‘none’

二、三步部署:报告、收敛、强制

最常见的错误是直接上Content-Security-Policy头。这样会把站内所有未列入白名单的脚本立刻拦掉,GA4、在线客服、像素全挂,业务方第一时间投诉。

正确做法是先用Content-Security-Policy-Report-Only头。浏览器只报告违规、不拦截,你收集两周日志,把所有真实用到的域名加进白名单。

场景示例一:给WordPress站加CSP,先发Report-Only。

两天后报告里出现googletagmanager.com、hotjar.com、youtube.com三个域名。

逐条加进script-src和frame-src。两周后报告每天只剩个位数,再切到强制模式。

阶段 响应头 行为 持续时间
第一步 Content-Security-Policy-Report-Only 只报告不拦截 14天
第二步 同上,逐步补白名单 违规逐日下降 直到个位数
第三步 Content-Security-Policy 真正拦截 长期
全程 加report-to或report-uri 收集违规上报 一直开着

内联脚本是最大的坑。WordPress主题和很多插件直接在页面里写<script>,CSP默认拦内联。两个办法:一是把内联脚本抽到外部js文件,二是用sha256哈希放行具体那一段,而不是图省事开unsafe-inline。

style-src比script-src松一些。古腾堡编辑器和很多主题会输出内联style,style-src临时留着unsafe-inline问题不大。

但script-src绝对不能开unsafe-inline,这是CSP防XSS的核心防线。两条指令的松紧标准要区别对待。

三、绕过风险:别让CSP形同虚设

CSP配错等于没配。以下三种写法会让这道墙直接失效。

第一种是开unsafe-inline。它允许任何内联脚本执行,XSS注入的恶意脚本也能跑,CSP的核心防线就破了。第二种是开通配符*,等于不限制来源。第三种是漏了object-src,老浏览器还能通过Flash或插件绕过。

场景示例二:图省事在script-src里写了’unsafe-inline’。

结果两个月后站点被注入一段内联钓鱼脚本,CSP完全没拦住。

正确做法是用nonce,每个请求生成一次性随机数,只放行带这个数的内联脚本。

错误写法 风险 替代方案
script-src * 任意第三方脚本可加载 逐个列白名单域名
script-src ‘unsafe-inline’ XSS注入脚本照常执行 抽外部文件或用nonce/哈希
不写object-src 老插件可被滥用 object-src ‘none’
不配frame-ancestors 页面被恶意iframe嵌套 frame-ancestors ‘none’

CSP和合规是两回事。GDPR的Cookie同意是另一套机制,见独立站GDPR合规。CSP防的是脚本注入,不解决Cookie同意问题。服务器层面的登录和文件权限见独立站安全加固。

nonce和哈希怎么选?固定的内联脚本用哈希,动态生成的内联脚本用nonce。WordPress主题里固定的追踪代码段算哈希放行;每次请求内容不同的脚本块用nonce。

部署位置也有讲究。CSP响应头最好在Nginx或CDN层下发,不要靠PHP应用输出。应用层输出会在PHP故障时丢失,CDN层下发则稳定。

部署位置 稳定性 适用场景
Nginx配置 高,应用挂了也在 自建服务器
CDN规则 高,全球生效 用了Cloudflare等
PHP应用输出 低,故障时丢失 临时测试

报告接收端点也要专门建。report-uri指向的地址不能被CSP自己拦,否则报告发不出来。

独立站可以用现成的报告收集服务,或者在Nginx里配一个专门的log endpoint接收上报,写进单独的日志文件,不进主站流量日志。

报告量大了之后要聚合。每天可能收到几百条上报,逐条看不过来,按违反的指令和被拦域名分组统计,重点关注每天新增的域名。

有一个常见误区:以为CSP上了就万事大吉。实际上每次加新的第三方脚本、每次主题升级,都可能引入新的违规源。

所以切到Enforce之后,report-uri不能关。它是你发现”新加的脚本忘了加白名单”的唯一信号来源。

浏览器支持也要提一句。现代浏览器(Chrome、Edge、Firefox、Safari近两年版本)都完整支持CSP 3。老IE可以直接忽略,B2B独立站的访客基本不会用IE。

还有一个部署细节:CSP头要同时给http和https都下发。只在https上下发,http回源阶段的请求就不受CSP保护。

切到Enforce之后,建议保留一条Report-Only头并行存在一段时间,既拦截又上报,万一漏了什么还能在报告里看到。

CSP不是一次性项目。每加一个新的第三方脚本(客服、聊天、视频、广告),都要先在报告模式观察到它出现在违规里,再把对应域名加进白名单,然后才切到生产。

把CSP检查加进新脚本上线清单:加脚本的同时加白名单,不要等上线后被用户报”控件不显示”才想起CSP。

一个判断标准:如果某段第三方脚本无法被改造成外部文件、也无法计算哈希,那就要重新评估这个第三方是否值得用。依赖unsafe-inline的第三方服务本身就是技术债。

部署完成后,用浏览器控制台的Security面板逐页抽查。首页、产品页、博客页、表单页各看一遍,确认没有红色违规提示。

抽查不通过的页面,回到报告里找对应域名,补进白名单再观察一天。这个循环重复两三轮就稳了。

CSP不是一次部署完就结束的安全项,它和你的第三方脚本库一起长期演进。每加一个新脚本,都要同步更新一次白名单,千万别漏配任何一个新出现的第三方脚本域名。

报告来源 含义 处置
script-src违规 新脚本没加白名单 加域名或抽外部文件
img-src违规 图片外链来自新域 加CDN或改相对路径
frame-src违规 嵌入了新iframe 加视频域名或去掉
base-uri违规 页面被注入base标签 立即排查XSS

FAQ 常见问题

CSP应该一上线就用强制模式吗?
不要。先用Content-Security-Policy-Report-Only报告模式跑14天,浏览器只上报违规不拦截。把报告里出现的真实域名逐个加进白名单,等违规日志降到个位数后,再切到Content-Security-Policy强制模式。一上来就强制会把GA4、在线客服、广告像素全打挂。
WordPress主题里的内联脚本被CSP拦了怎么办?
不要图省事开unsafe-inline。两个正确做法:一是把内联脚本抽到独立的js文件,用src引入;二是对必须保留的内联脚本计算sha256哈希,在script-src里放行这个具体哈希。unsafe-inline会让XSS注入的恶意脚本也能执行,等于CSP白配。
CSP和HTTPS、HSTS是什么关系?
三者配合但不重叠。HTTPS管传输加密,HSTS强制浏览器只用HTTPS,CSP管浏览器侧能加载哪些脚本和资源。HTTPS装好是前提,CSP是在HTTPS之上再防XSS和未授权第三方脚本。只装CSP不装HTTPS没有意义。
为什么我的CSP报告里全是违规?
说明白名单不全。每条违规报告会指出被拦的脚本来自哪个域名,把这个域名加进对应的指令(script-src、img-src、frame-src等)。第三方统计、客服、视频嵌入是最常见的漏网之鱼。报告模式的意义就在这里,先收集干净再强制。
CSP能挡住SQL注入吗?
不能。CSP工作在浏览器侧,只控制资源加载,挡不住服务器端的SQL注入。SQL注入要靠数据库预处理和输入过滤解决。CSP主要防的是XSS——攻击者注入的恶意脚本被CSP拦住不执行,但注入本身已经发生在服务器。
report-uri还需要保留吗?
需要。即使切到Enforce强制模式,也要保留report-to或report-uri持续收集新出现的违规。新加的第三方脚本、主题更新引入的内联代码,都会先在报告里出现。建议把违规上报接到日志服务,每天看一眼异常域名。

写在最后

CSP的难点不在写规则,在于耐心跑报告模式。两周的观察期换来后续几年的脚本可控,这笔账划算。

落地三步:先发Report-Only跑14天;按报告把真实域名逐个收进白名单,用哈希替代unsafe-inline;违规降到个位数后切强制,同时保留report持续监控。

一句可以记住的话:CSP的安全水位取决于白名单有多干净——留着unsafe-inline和通配符,这道墙等于没砌。

环形山
环形山 环形山专家

机构官方 · 环形山 Crater

环形山(Crater)是一家专注 WordPress 独立站开发 + Google SEO + GEO 全域增长的出海服务公司,服务智能硬件、工业装备、SaaS、新能源等赛道。

产品能力方法论白皮书行业报告
965篇分享
78篇知识库