Caloogy 文章
English

CSP 与 SRI:只让自己的代码运行

日期
2026 年 5 月 18 日
系列
Web 安全
阅读
3 分钟
主题
WebCspSriXss

一句话版本

网页里的任何一段 JavaScript,都拥有和页面本身一样的权力:能读表单、改内容、以你的身份发请求。 CSP(内容安全策略)告诉浏览器"只许运行来自这些地方的脚本",把注入进来的恶意脚本挡在门外;SRI(子资源完整性)告诉浏览器"这个脚本的哈希必须是这个值",第三方服务器上的文件被人改了也会被拒绝执行。对于处理钱和密码的页面,最稳妥的做法是:零第三方脚本。

全文分三层:

  1. 通俗理解:为什么一行外部脚本就能毁掉一个网站
  2. 实际用处:英航 Magecart、polyfill.io、Ledger Connect Kit
  3. 原理与工程要点:CSP 指令逐条解读、SRI 的写法、违规上报

第一层:通俗理解

1.1 外部脚本 = 把家门钥匙交给别人

很多网站会这样引用第三方代码:

<script src="https://cdn.某统计服务.com/track.js"></script>

这行代码的意思是:"去那台服务器拿一段代码,以我的身份运行。"如果那台服务器被黑了,或者域名换了主人,它返回什么代码,你的页面就运行什么代码,包括"把用户输入的信用卡号发到我这里"。

1.2 两道防线

防线 管什么 比喻
CSP 哪些来源的代码允许运行 门卫只放名单上的公司员工进门
SRI 某个文件的内容必须是什么 包裹上贴了封条,封条破了就不收

第二层:实际用处

事件 发生了什么 哪道防线能挡
英国航空(2018) 攻击者改了 BA 网站上的一个 JavaScript 库,加了 22 行代码,把支付页面的卡号发往仿冒域名。约 38 万笔交易受影响,后被英国监管机构罚款 2000 万英镑 CSP 的 connect-src 可以阻止数据发往陌生域名
polyfill.io(2024) 一个被十多万网站引用的公共脚本服务,域名被转卖后开始向部分访客投放恶意跳转代码 SRI 会让被篡改的文件无法执行;更彻底的是不引用外部 CDN
Ledger Connect Kit(2023) 攻击者钓鱼拿到了一名前员工的 npm 账号,发布了带"盗币程序"的新版本;大量 DeFi 前端通过 CDN 自动加载了最新版,几小时内用户损失约 60 万美元 锁定版本 + SRI,或者把依赖打包进自己的代码

这一类攻击统称 Magecart(网页侧录)或供应链攻击:攻击者不碰你的服务器,而是改你信任的第三方代码。

Caloogy Wallet 也采用了零第三方脚本加严格 CSP 的做法。


第三层:原理与工程要点

3.1 一个严格的 CSP

Content-Security-Policy:
  default-src 'none';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'none';
  form-action 'self';
  report-to csp-endpoint
指令 含义
default-src 'none' 默认什么都不许,下面逐项放行
script-src 'self' 只运行本站的脚本文件;内联脚本和 eval 一律禁止,这是挡住大多数 XSS 的关键
connect-src 页面只能向这些地址发 fetch / WebSocket,即使恶意脚本混进来,也偷不出数据
frame-ancestors 'none' 不能被嵌入 iframe,防止点击劫持
base-uri 'none' 防止注入 <base> 标签篡改所有相对链接
form-action 'self' 表单只能提交到本站
report-to 违规时浏览器自动上报,可以把它当作入侵告警

上线前可以先用 Content-Security-Policy-Report-Only 只报告不拦截,观察一段时间,确认没有误伤再正式启用。

3.2 SRI 的写法

<script src="https://cdn.example.net/lib-4.2.1.min.js"
        integrity="sha384-(文件内容的 SHA-384 哈希,base64 编码)"
        crossorigin="anonymous"></script>

浏览器下载文件后计算哈希,对不上就拒绝执行。它依赖哈希函数的抗第二原像性质:攻击者无法造出一个内容不同、哈希却相同的恶意文件(见《生日悖论与哈希碰撞》)。

局限:SRI 只能用于固定版本的文件。如果引用的是"永远最新版"的地址,内容会变,哈希也会变,SRI 就用不了。而"永远最新版"恰恰是 Ledger Connect Kit 事件的根源。

3.3 零第三方脚本

对于签名、支付、登录这类页面,最稳妥的做法是:

  1. 所有依赖锁定版本,在构建时打包进自己的代码,不从任何 CDN 运行时加载;
  2. 不放统计、广告、客服聊天等任何第三方脚本;
  3. 依赖升级走人工审查,不开自动合并;
  4. 注意托管平台本身也可能往页面里插东西(统计、性能优化、邮箱混淆脚本),需要关掉,或者加 Cache-Control: no-transform 并定期巡检(见《Bybit事件与前端完整性》)。

3.4 CSP 防不了什么

这两种情况要靠部署链路的安全和线上完整性巡检来防。


总结

层次 要点
直觉 外部脚本以你的身份运行,等于交出家门钥匙
CSP 限定代码来源和数据去向;禁内联和 eval;违规上报当告警
SRI 用哈希给文件贴封条;只适用于固定版本
最佳实践 敏感页面零第三方脚本;依赖锁版本、打包进来、人工审查