FlowingDev

SRI 详解:你网站资源的“数字指纹”

学习一下 SRI(子资源完整性)如何利用加密哈希,来保护你的网站免受来自第三方 CDN 的恶意脚本和样式表的侵害。

试用工具: SRI 哈希生成器

一句话概括

SRI (Subresource Integrity,子资源完整性) 是一个安全功能,它能让浏览器验证从外部来源(比如 CDN)获取的文件是否被暗中篡改过。

它解决了什么问题

想象一下这个场景:你正在开发一个时髦的新 web 应用。为了让它快如闪电,你用了个 CDN(内容分发网络)来提供像 React、Vue 这样的通用库,甚至只是一些花哨的字体。这是标准操作。你的用户能获得更快的体验,因为这个文件很可能因为访问过其他网站而已被缓存,或者它是由一个离用户物理位置更近的服务器提供的。双赢,对吧?

基本上是。但你刚刚引入了一个巨大的信任环节。你在信任那个 CDN 提供商会永远提供你想要的那个确切的文件。但要是那个 CDN 被黑了呢?攻击者可能会把那个友好无害的 react.min.js 换成一个恶意版本:react.min.js-外加一个加密货币矿工和密码窃取器。

突然之间,这些恶意代码就在你的网站上运行起来了,还获得了你用户浏览器的完全信任。它能吸走登录凭据、篡改你的页面,或者把你的访客拉入僵尸网络。这就是典型的供应链攻击,而且它非常吓人,因为你自己的服务器啥错事都没干。你只是在错误的时间信任了错误的第三方。

在 SRI 出现之前,浏览器没有原生的机制来防御这种情况。开发者们会用一些变通的办法,但都很笨拙。SRI 由 W3C 创建,就是为了正面解决这个特定问题。它提供了一个简单、标准化的方式来告诉浏览器:“Hey,去把这个脚本拿过来,但在运行它之前,务必确认它就是我想要的那一个。只要有一个字节不对,就立马把它扔掉,然后告诉我。”

底层工作原理

SRI 是简单的 HTML 属性和一些严肃的密码学原理的巧妙结合。我们来分解一下。

integrity 属性

魔法始于一个你可以添加到 <script> 和 <link> 标签上的新属性。它的名字恰如其分,就叫 integrity。

<script
  src="https://code.jquery.com/jquery-3.6.0.min.js"
  integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3"
  crossorigin="anonymous"></script>

这个属性包含一个字符串,字符串里有两部分:一个哈希算法前缀(这里是 sha384-)和一个 Base64 编码的加密哈希值。这就是你期望收到的文件的“数字指纹”。

哈希:一个数字指纹

加密哈希函数是一种数学算法,它接收一个输入(比如整个 JavaScript 文件的内容),然后生成一个短的、固定长度的字符串,这个字符串就叫哈希(hash)。你可以把它想象成一个超级加强版的校验和(checksum)。

这些哈希有几个关键特性:

  • 确定性(Deterministic): 同一个输入文件总是会生成完全相同的哈希值。
  • 雪崩效应(Avalanche Effect): 只要输入文件里改动一个字符——加个空格,改个变量名——生成的哈希值就会变得面目全非,完全不一样。
  • 单向性(One-way): 这个过程几乎是不可逆的。你不能拿着哈希值反推出原始文件的内容。

SRI 标准支持三种安全的哈希算法:SHA-256、SHA-384 和 SHA-512。数字指的是哈希值的比特长度,通常越大越强。SHA-384 是一个很棒的通用选择。

所以,当你准备链接到一个 CDN 文件时,你首先要生成它的哈希值。你基本上是在那一刻给文件拍了张快照,然后告诉浏览器:“这才是真正的 jquery-3.6.0.min.js 应该有的样子。”

crossorigin 属性

看到例子里的 crossorigin="anonymous" 了吗?它可不是为了好看,而是强制性的。浏览器要想从一个不同的源(比如你的网站 my-app.com 从 code.jquery.com 获取脚本)获取资源并检查其内容以进行 SRI 校验,它需要通过跨源资源共享(CORS)获得许可。

设置 crossorigin="anonymous" 告诉浏览器在发起请求时,不要发送任何用户凭据,比如 cookie 或 HTTP 认证头。这是安全和隐私方面的必备操作。如果你忘了这个属性,浏览器会拒绝执行完整性检查,并直接阻止该资源加载,导致你的网站挂掉。

整合起来:浏览器的检查清单

当浏览器遇到一个带有 integrity 属性的标签时,它会遵循这个严格的流程:

  1. 它看到 <script> 标签,并注意到 src、integrity 和 crossorigin 属性。
  2. 它向 src URL 发送一个文件请求。因为有 crossorigin,这是一个 CORS 请求。
  3. 文件被下载下来。
  4. 关键一步:在执行任何代码之前,浏览器会使用 integrity 属性中指定的相同算法(比如 sha384),对下载下来的文件内容计算它自己的哈希值。
  5. 然后,它将自己新鲜计算出的哈希值与你在属性中提供的哈希值进行比较。
  6. 如果匹配: 太棒了!文件是纯正的。浏览器会执行脚本或应用样式表。
  7. 如果不匹配: 红色警报!浏览器会认为文件被篡改了。它会完全丢弃这个文件,并且不会执行它。然后,它会在开发者控制台抛出一个 Failed to find a valid digest 错误。你的网站可能看起来坏掉了(比如图表或字体不见了),但你成功地躲过了一劫。

真实世界的故事

被篡改的分析仪表盘

一个市场团队依赖一个仪表盘来可视化他们的活动数据,这个仪表盘使用了一个第三方图表库,从一个冷门的 CDN 上拉取。那位开发者刚读完安全最佳实践,给库的 <script> 标签加上了 SRI 哈希。一个周一的早晨,那个 CDN 遭受了短暂的入侵。一个攻击者把那个流行的图表库换成了一个只会显示巨大、嘲讽的 ASCII 艺术鬼脸的脚本。

当市场团队加载他们的仪表盘时,图表都坏了,只看到一个个空白的框框。他们很烦躁地给 IT 部门打了电话。开发者检查了浏览器控制台,看到了那个美妙绝伦的 SRI 验证错误。浏览器检测到了被修改的文件,拒绝运行它,从而阻止了页面被篡改。这事儿最终没有演变成一场重大的安全事故和高管们的惊慌失措,而只是一个 15 分钟就能搞定的调查,最后暂时把资源指向了另一个 CDN。

教训: SRI 把一场潜在的安全灾难,变成了一个可控的可用性问题。

鬼鬼祟祟的加密货币矿工

一个托管在免费 CDN 上的、轻量级的 JavaScript 工具库是独立开发者的心头好。一个攻击者获取了 CDN 的访问权限并修改了库文件,添加了几行混淆过的代码,用于启动一个 WebAssembly 加密货币矿工。文件大小几乎没变,库的核心功能也仍然完美工作。

没有使用 SRI 的网站突然开始导致用户的笔记本电脑风扇狂转、电池飞速消耗。用户抱怨网站卡顿,但问题很难诊断,因为网站本身看起来没问题。然而,那些实现了 SRI 的网站却安然无恙。它们的浏览器阻止了被修改的脚本,虽然工具函数坏掉了,但用户的 CPU 是安全的。

教训: SRI 不仅能抓住明显的页面篡改,还能捕获那些会损害你网站声誉的、隐蔽的寄生式攻击。

被遗忘的字体更新

一位设计师坚持要使用某个特定版本的字体,该字体由一家第三方字体公司通过他们的 CDN 提供。开发者尽职地复制了 <link> 标签,连同它的 SRI 哈希一起。网站上线了,看起来很棒。六个月后,那家字体公司更新了字体文件,添加了新的货币符号并改进了字距。这是一次合法的、有益的更新。

突然,网站的文本变回了丑陋的默认 Arial 字体。开发者一脸懵逼,直到他们检查了控制台,看到了 SRI 错误。浏览器正确地阻止了这个新的、被修改过的字体文件,因为它的哈希值和 HTML 里的旧哈希值不再匹配。这次“攻击”其实只是一次无害的更新,但 SRI 尽职尽责地完成了它的工作。修复很简单:为更新后的字体生成一个新的哈希值,然后部署这个改动。

教训: SRI 强制执行严格的版本控制。它能保护你免受恶意更改和意料之外的上游更新,迫使你对自己使用的资源保持清晰的认知和意图。

常见错误和陷阱

  • 忘记 crossorigin="anonymous"。 这是头号错误。没有它,浏览器就没有 CORS 权限来检查资源,所以出于安全考虑,它会直接阻止资源。完整性检查根本不会发生。你的脚本或样式就是加载失败。
  • 对错误的东西进行哈希计算。 你必须对浏览器收到的确切文件内容进行哈希。如果你链接的是 CDN 上压缩过的版本,就不要对本地未经压缩的版本进行哈希。不要对 URL 字符串本身进行哈希。你需要的是文件正文的哈希。
  • 使用弱哈希算法。 MD5 和 SHA-1 有已知的漏洞,不应该用于安全目的。规范要求浏览器至少支持 SHA-256、SHA-384 和 SHA-512。请坚持使用这几种。
  • 在合法更新后,没有更新哈希值。 SRI 是个功能,不是 bug。如果你链接的文件因任何原因更新了,你必须生成一个新的完整性哈希,并更新你的 HTML integrity 属性。不这样做会导致资源被阻止。
  • 以为它能保护你自己的服务器。 SRI 是为验证第三方资源而设计的。如果攻击者已经入侵了你的服务器,能修改你的 HTML 文件,那他们也就能把 SRI 哈希值改成跟他们的恶意脚本相匹配的值。对于同源资源,它没有任何好处。

为什么它值得你关注

每当你写下指向不受你控制的域名的 <script src="..."> 或 <link rel="stylesheet" href="..."> 时,你都应该想到 SRI。

它是现代 Web 安全的基础组成部分。在一个由 NPM、CDN 和复杂的第三方依赖网络构成的世界里,你的供应链就是一个巨大的攻击面。SRI 是加固这个攻击面最简单、最有效的工具之一。它是你对抗被黑 CDN 的第一道防线。与内容安全策略(CSP)搭配使用,它能提供强大的分层保护。

添加 SRI 在你引入一个资源时只需要多花几秒钟,但它能在未来为你免去无数的麻烦。它把一个无声的、危险的漏洞利用,转变成了一个响亮的、安全的失败。

深入了解

理论搞定,动手试试吧——100% 在你的浏览器中运行。

试用工具: SRI 哈希生成器