一句话版本
网页里的任何一段 JavaScript,都拥有和页面本身一样的权力:能读表单、改内容、以你的身份发请求。 CSP(内容安全策略)告诉浏览器"只许运行来自这些地方的脚本",把注入进来的恶意脚本挡在门外;SRI(子资源完整性)告诉浏览器"这个脚本的哈希必须是这个值",第三方服务器上的文件被人改了也会被拒绝执行。对于处理钱和密码的页面,最稳妥的做法是:零第三方脚本。
全文分三层:
- 通俗理解:为什么一行外部脚本就能毁掉一个网站
- 实际用处:英航 Magecart、polyfill.io、Ledger Connect Kit
- 原理与工程要点: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 零第三方脚本
对于签名、支付、登录这类页面,最稳妥的做法是:
- 所有依赖锁定版本,在构建时打包进自己的代码,不从任何 CDN 运行时加载;
- 不放统计、广告、客服聊天等任何第三方脚本;
- 依赖升级走人工审查,不开自动合并;
- 注意托管平台本身也可能往页面里插东西(统计、性能优化、邮箱混淆脚本),需要关掉,或者加
Cache-Control: no-transform并定期巡检(见《Bybit事件与前端完整性》)。
3.4 CSP 防不了什么
- 攻击者如果能直接改你服务器上的文件(
'self'的来源),CSP 照样放行; - 构建过程被投毒,恶意代码已经在"自己的代码"里了。
这两种情况要靠部署链路的安全和线上完整性巡检来防。
总结
| 层次 | 要点 |
|---|---|
| 直觉 | 外部脚本以你的身份运行,等于交出家门钥匙 |
| CSP | 限定代码来源和数据去向;禁内联和 eval;违规上报当告警 |
| SRI | 用哈希给文件贴封条;只适用于固定版本 |
| 最佳实践 | 敏感页面零第三方脚本;依赖锁版本、打包进来、人工审查 |