CSP,给你的网页发执照

0 阅读7分钟

上篇我们聊了同源策略——浏览器的国境线。它解决的是"谁能读我的数据"。但有一个问题它管不了:如果恶意代码已经混进了你的页面呢?

一段注入的 <script>,跟你的业务代码同源,同源策略不会拦它。它拿 Cookie、改 DOM、往外发请求,畅通无阻。

同源策略管的是"别人能不能进来看",CSP 管的是"进来之后能做什么"。

这就是今天要聊的 Content Security Policy(内容安全策略)。它不是同源策略的替代品,而是补上了同源策略的盲区——从"访问隔离"升级到"执行权限控制"

一、先搞清楚一个关键区别

同源策略和 CSP 经常被混为一谈,但它们的分工完全不同:

同源策略(SOP)CSP
谁定的规矩浏览器内置,你改不了你自己配,主动声明
控制方向管数据"流出"——阻止跨源读取管资源"流入"——限制页面能加载什么
核心问题"别人能不能偷看我的数据?""我的页面能执行谁的代码?"
类比国境线上的护照检查城内的营业执照制度

同源策略像皮肤,是天生的被动屏障;CSP 像白细胞,是你主动部署的执行层。两层加一起,才是完整的免疫系统。

一个管"谁能进来",一个管"进来的人能干什么"。

二、CSP 到底在防什么

一个字:XSS

跨站脚本攻击(XSS)至今仍是 OWASP Top 10 里的常客。攻击者把恶意代码注入你的页面,浏览器分不清这是你写的还是攻击者写的,照单全收。

注入的姿势有很多种:

注入形式示例
恶意外部脚本<script src="https://evil.com/hack.js">
内联脚本<script>偷Cookie</script>
事件处理器<img onmouseover="偷Cookie">
javascript: URL<a href="javascript:偷Cookie">
危险 APIeval("偷Cookie")

CSP 的思路很直接:既然我分不清好代码和坏代码,那我就只允许"持证上岗"的代码执行。

没有执照?不管你是谁,一律拦截。

三、执照怎么发

CSP 通过 HTTP 响应头传递给浏览器:

Content-Security-Policy: script-src 'self'; object-src 'none'

这句话翻译成人话:只允许加载同源的脚本,禁止所有插件。

核心指令就这么几个:

指令管什么
default-src兜底策略,没单独配的都用它
script-srcJavaScript——最关键的一条
style-srcCSS 样式表
img-src图片
connect-srcXHR / Fetch / WebSocket
frame-ancestors谁能用 iframe 嵌入你(防点击劫持)
object-srcFlash 等插件(建议直接 'none'
base-uri<base> 标签(建议直接 'none'

指令后面跟的值叫"源表达式",常用的有:

• 'self' — 只允许同源

• 'none' — 完全禁止

• https://cdn.example.com — 指定域名

• 'nonce-随机值' — 持有对应令牌的脚本

• 'sha256-哈希值' — 内容匹配的脚本

CSP 的设计哲学是"默认拒绝,显式放行"——跟防火墙的白名单规则一模一样。

四、白名单为什么靠不住

直觉上,列一份域名白名单似乎就够了:

Content-Security-Policy: script-src trusted-cdn.com analytics.com

但 2016 年 Google 安全团队的一篇论文(CSP Is Dead, Long Live CSP! )给了行业一记重锤。他们扫描了超过 10 亿个主机名,分析了 26,011 种唯一的 CSP 策略,结论是:

指标数据
白名单 CSP 可被绕过的比例75.81%
对 XSS 防御完全无效的策略99.34%
前 15 个最常被白名单引用的域中不安全的14/15 个

为什么?因为白名单域上往往存在 JSONP 接口、Angular 模板引擎、可控回调参数这些"合法但可被利用"的入口。攻击者不需要绕过你的 CSP,只需要找到你白名单里那个域的一个 JSONP 接口:

<script src="trusted-cdn.com/jsonp?callback=alert(1)//"></script>

CSP 看到 trusted-cdn.com——放行。攻击完成。

这就像城市的安保只看营业执照上的公司名,不看员工具体在干什么。名单再长,也防不住内部人作恶。

白名单管的是"你从哪来",但攻击者早就学会了借壳。

五、Strict CSP:从查身份到查指纹

Google 那篇论文的解决方案是 Strict CSP——不再信任域名,改为信任具体的代码

两种方式:

Nonce(一次性令牌)

服务端每次响应生成一个随机值,同时放进 CSP 头和 <script> 标签:

Content-Security-Policy: script-src 'nonce-a1b2c3d4'
<script nonce="a1b2c3d4" src="/main.js"></script>
<script nonce="a1b2c3d4">console.log("合法")</script>

浏览器对比两边的 nonce,匹配才执行。攻击者注入的脚本没有 nonce,直接被拦。

关键要求:每次请求生成新的 nonce,不可预测,不可复用。

Hash(内容指纹)

对脚本内容算 SHA-256,把哈希值写进 CSP 头:

Content-Security-Policy: script-src 'sha256-abc123...'

浏览器收到脚本后重新算哈希,匹配才执行。内容被篡改一个字符,哈希就对不上。

NonceHash
适合场景动态渲染(SSR / 模板引擎)静态页面 / 客户端渲染
每次请求变化✅ 是❌ 否
脚本改了要更新吗不用必须重新算哈希

Nonce 像每次进门换一把新钥匙,Hash 像给每个包裹贴了防拆封条。两者的共同点是:不再信任"你从哪来",只信任"你是不是那个人"。

这跟安全领域的零信任(Zero Trust)架构是同一个思路——永远验证,绝不默认信任。

六、strict-dynamic:信任链的传递

现实中你会遇到一个问题:你信任的主脚本 main.js 可能会动态加载其他脚本,这些子脚本没有 nonce,会被 CSP 拦住。

strict-dynamic 解决这个问题:被 nonce/hash 信任的脚本,它加载的子脚本也自动被信任。

Content-Security-Policy: script-src 'nonce-a1b2c3d4' 'strict-dynamic'
// main.js(有 nonce,被信任)
const s = document.createElement("script");
s.src = "analytics.js";
document.head.appendChild(s);
// analytics.js 也被允许执行

这像公司的信任传递:CEO 信任 VP,VP 招进来的人也获得一定权限。但风险也在这里——如果被信任的脚本本身有漏洞,信任链就被污染了。

所以 strict-dynamic 是一个务实的折中:用可控的信任范围,换取第三方脚本的兼容性。

七、推荐的 Strict CSP 模板

MDN 和 Google 推荐的生产环境配置:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  upgrade-insecure-requests;
  report-to csp-endpoint

拆解一下:

指令作用
script-src 'nonce-...' 'strict-dynamic'只执行持令牌的脚本,信任链可传递
object-src 'none'彻底封死 Flash 等插件入口
base-uri 'none'防止 <base> 标签被利用做路径劫持
frame-ancestors 'none'禁止被 iframe 嵌入(防点击劫持)
upgrade-insecure-requests自动把 HTTP 升级为 HTTPS
report-to违规行为上报到你的监控端点

先用 Report-Only 模式测试,确认不影响功能,再切到强制模式。  这是部署 CSP 最重要的实操原则——先观察,再执行,别一上来就把自己的网站搞挂了。

八、一个架构原则

CSP 给前端架构带来了一个深层约束:第三方脚本的接入,必须经过策略层审批,不能直接穿透到业务域。

没有 CSP 的时代,产品经理说"加个统计代码",开发就往页面里塞一个 <script>。这个脚本能做什么、会加载什么、数据发到哪里,没人知道。

有了 CSP,每个外部脚本都必须通过你的 nonce 或 hash 审批。这不是增加了负担——这是把"谁能在我的地盘执行代码"这个问题,从隐性变成了显性。

好的架构从来不是没有约束,而是约束放在了正确的位置。

CSP 不是限制,是治理。同源策略画了国境线,CSP 建了城内的执法体系。


如果你只想带走一句话,我建议记这个:

同源策略管"谁能进来",CSP 管"进来之后能做什么"。安全不是一堵墙,是一套分层的权限体系。

参考原文:

• MDN Web Docs — Content Security Policy (CSP)

• Google Security Team — CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy(2016)

qrcode_for_gh_6a9e7f3719d6_344.jpg