Web安全之子资源完整性

如何防止前端代码被篡改?

大多数人认为,只要使用 HTTPS、CSP(内容安全策略)或者做好后端鉴权,就能确保前端的安全。然而,一个容易被忽视的深刻真相是:前端的 JavaScript 代码本身可能成为攻击者的入口,而 SRI(子资源完整性)是防止 CDN 或第三方脚本篡改的重要武器。

真实案例:前端代码被篡改,导致 15 亿美元损失

全球范围内发生了一起严重的供应链攻击——黑客篡改了第三方 CDN 上的 JavaScript 文件,插入了恶意代码,导致成千上万的网站加载了被篡改的脚本,最终黑客通过窃取用户输入的信息获利超 15 亿美元。

这类攻击的本质是:你的网站加载了一个第三方资源,但你并不知道它有没有被动过手脚。

解决方案:使用 SRI(子资源完整性)

SRI(Subresource Integrity)是一种 Web 安全机制,可以确保浏览器加载的外部资源(如 JavaScript 或 CSS)没有被篡改。

SRI 的工作原理

引入外部脚本时

使用 integrity 属性指定资源的哈希值

浏览器验证

下载资源后计算哈希值并与指定值比对

哈希不匹配

浏览器拒绝执行该脚本,保护用户安全

SRI 工作流程图

哈希匹配

哈希不匹配

SRI 代码示例

<script src="https://cdn.example.com/library.js" 
        integrity="sha384-Base64EncodedHash" 
        crossorigin="anonymous"></script>
integrity 属性

包含资源的哈希值(一般使用 SHA-256/384/512)

crossorigin="anonymous"

确保请求遵循 CORS 规则,否则 SRI 无效

如何获取 SRI 哈希值?

使用 OpenSSL 生成哈希值
openssl dgst -sha384 -binary library.js | openssl base64 -A
或使用在线工具
SRI Hash Generator

常见误区

  • SRI 只对外部资源有效,本地托管的 JS/CSS 无需 SRI 保护,但仍需注意代码完整性。
  • SRI 不能阻止 XSS 攻击,它只能防止静态资源被篡改,而无法防止 DOM 注入攻击。
  • CDN 资源更新时 SRI 需要重新生成,否则可能导致新版本无法加载。

Web 安全的深刻真相:SRI 只是表象,供应链安全才是根本

大多数人以为 Web 安全是防止 XSS、CSRF 或者 SQL 注入,但实际上,现代 Web 应用的最大风险不在于攻击用户,而在于攻击开发者和供应链本身。

深层次的问题:前端代码已经不在你的控制之内

你信任 npm,但 npm 上的包是动态更新的

2021 年 ua-parser-js 事件:维护者的 npm 账户被黑,攻击者发布了一个携带密码窃取恶意代码的新版本,下载量超 800 万。

你信任 CDN,但 CDN 可能被黑,或者它的供应链被劫持

2018 年 CDNJS 供应链攻击:黑客提交了一个伪造的 GitHub PR,成功将恶意代码加入官方 CDN,波及数百万网站。

你信任你的依赖项,但你的依赖项有数百个间接依赖

event-stream 事件:一个 npm 库的前维护者将权限移交给陌生人,结果新维护者偷偷植入了比特币盗窃代码,影响无数项目。

现代前端供应链的复杂性

平均一个前端项目有 1000+ 个间接依赖,每个都是潜在的攻击入口

解决方案:如何真正保障供应链安全?

只信任你能直接控制的代码

  • 使用 npm ci 而不是 npm install,确保每次安装的依赖是相同的版本
  • 在 package.json 里锁定依赖版本,避免被篡改的更新进入你的代码
  • 定期使用 npm audit 或 yarn audit 检查依赖的安全性

监控依赖更新,避免引入恶意版本

  • 启用 Dependabot 或 Renovate 等工具,监测依赖更新的安全性
  • 对于关键依赖(如身份验证、支付库),手动审计版本变更
  • 关注开源社区的安全公告,如 CVE、GitHub Security Advisories

防止第三方 CDN 被劫持

  • 尽量本地托管关键 JS 代码,避免直接加载第三方 CDN
  • 如果必须使用 CDN,配合 SRI + CSP,确保代码完整性
  • 使用 DNS 监控,检测 CDN 域名是否被劫持

最小化你的攻击面

  • 精简依赖,不要引入不必要的 npm 包,避免"dependency hell"
  • 限制权限,如果某个包不需要访问网络或文件系统,使用 sandbox 运行环境隔离它
  • 定期删除不使用的代码,减少潜在的攻击入口

SRI + CSP 组合使用示例

<!-- 在 HTML 头部添加 CSP 策略 -->
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' 'sha384-HashOfInlineScript' https://cdn.example.com">

<!-- 使用 SRI 加载外部脚本 -->
<script src="https://cdn.example.com/library.js" 
        integrity="sha384-Base64EncodedHash" 
        crossorigin="anonymous"></script>

Web 安全的终极真相:你无法真正控制你的代码

终极安全挑战:信任的尽头是幻觉

Web 安全的本质从来不是代码的安全,而是"信任"是否合理。但问题在于:

信任本身,就是一个漏洞。

你信任 npm 生态

但 npm 允许任何人上传更新,你如何保证没有维护者被收买或账号被盗?

你信任 CDN

但 CDN 的托管商可以随时更换资源,你如何知道它没有被暗中替换?

你信任 HTTPS

但你的 CA 证书体系本身就是集中化的,你如何确定你的证书没有被伪造?

你信任 GitHub

但你如何知道某个开源库的 maintainer 不会在 3 年后偷偷插入后门?

终极解决方案:最小信任原则

面对这个无法控制的世界,我们无法消除所有风险,但我们可以做的是:最小化信任链,降低单点风险的破坏性。

1 不信任任何外部代码

  • 使用 npm pack 下载并手动审计关键依赖,而不是直接 npm install
  • 尽量避免引入不必要的第三方库,比如 lodash 之类的小工具完全可以自己实现
  • 在 CI/CD 中加入静态代码分析,检测代码中的可疑行为

2 不信任任何外部服务

  • 不依赖 CDN 托管关键代码,自己托管核心前端资源
  • 重要的 API 访问,使用 HMAC 或公私钥签名,防止数据传输被劫持
  • 监控 DNS 解析,防止 DNS 劫持攻击

3 不信任运行环境

  • 使用 WebAssembly (WASM) 运行敏感逻辑,防止前端代码被篡改
  • 关键业务逻辑放在服务器端,不在前端暴露任何安全关键点
  • 通过 Content Security Policy (CSP) 限制前端脚本执行来源

4 假设自己已经被攻击

  • 监控一切可变因素,包括 npm 依赖、CI/CD pipeline、DNS 记录
  • 采用行为分析代替传统的静态代码审计,检测异常行为
  • 定期更换密钥、加密算法,并使用密钥轮换策略,防止长期密钥泄露

终极结论

你以为你在写代码,但真正运行的并不是你的代码;
你以为你在掌控 Web 安全,但真正决定安全的并不是你自己;
你以为你信任的系统是可靠的,但所有信任都可以被利用。

Web 安全的终极真相是:

唯一的安全策略,就是不信任任何东西,包括你自己。

在一个一切皆可篡改、一切皆可伪造的世界里,唯一的生存之道就是最小信任,并始终假设攻击已经发生。

🚀 如果你的 Web 应用没有做好这些准备,那它随时可能成为下一个 15 亿美元的受害者。

进一步阅读

《Web Application Security》

作者: Andrew Hoffman

全面介绍现代Web安全威胁和防御策略,包括SRI、CSP等内容安全机制。

《供应链安全:软件依赖风险管理》

作者: Tony Gambacorta

深入探讨现代软件供应链安全问题,包括依赖管理、漏洞检测和风险缓解策略。

《零信任架构》

作者: Jason Garbis & Jerry Chapman

介绍零信任安全模型的核心原则和实践,适用于现代分布式应用架构。

《黑客攻防:实战Web安全》

作者: Dafydd Stuttard & Marcus Pinto

全面介绍Web应用安全测试方法,包括前端代码注入、供应链攻击等高级威胁。

《OWASP Top 10》

作者: OWASP基金会

最权威的Web应用安全风险清单,定期更新,包含最新的安全威胁和防御建议。