了解子资源完整性
已发表: 2022-03-10 如果您曾经使用过 CDN 托管版本的 JavaScript 库,您可能已经注意到 script 标签上的一个奇怪的integrity
属性。 此属性包含看似无穷无尽的字母数字垃圾,您可能会在寻求更清晰的代码时将其删除。
所有这些垃圾实际上都是一个非常有用的安全功能,称为子资源完整性 (SRI),它可以帮助保护您的网站免受某些类型的黑客攻击和危害。 在本文中,我们将了解 SRI 是什么,它如何帮助保护您,以及如何在您自己的项目中开始使用它,而不仅仅是用于托管在 CDN 上的文件。
一点历史
早在 JavaScript 是 HTML 和 CSS 的表亲的时代,我们不需要过多考虑如何将我们的脚本用作我们网站的攻击媒介。 大多数站点都托管在我们自己的托管基础设施某处的单个物理服务器上,当涉及到安全最佳实践时,它是我们考虑防御的服务器。
随着浏览器的功能越来越强大,网络连接越来越胖,我们开始使用越来越多的 JavaScript,最终,可重用的 JavaScript 库开始涌现。 在早期,像 script.aculo.us、Prototype 和 jQuery 这样的库开始在希望在页面中添加更多交互性的开发人员中获得采用。
随着这些添加的库和后续插件的增加,页面重量也随之增加,不久之后我们开始认真考虑前端性能。 内容交付网络 (CDN) 之类的资源,以前是大公司的储备,现在已经成为日常人们构建时髦网站的普遍方式。
一路上,一些明亮的火花注意到网站都在请求他们自己的通用库副本——比如最新的 jQuery——如果这些库的通用 CDN 版本可以被每个站点使用,那么用户就不会t 需要继续下载相同的文件。 他们会在第一个站点使用该文件时受到打击,但随后它将位于本地浏览器缓存中,并且可以跳过每个后续站点的下载。 天才!
这就是为什么您会使用jsdelivr.com
类的 URL 看到您喜欢的库的 CDN 链接的原因——他们使用通用 CDN 来托管文件,以便他们的用户看到性能优势。
会出什么问题?
这仍然是一种很好、实用的工作方式,但它确实引入了潜在的攻击媒介。 假设现在是 2012 年,每个人都在使用全新的 jQuery 1.8。 回到传统的做事方式,每个人都可以将自己的 jQuery 1.8 文件作为自己网站的一部分托管在自己的服务器上。
如果你是某种邪恶的演员——比如某种基于 jQuery 的 Hamburglar——并且已经想出了一种偷偷摸摸的方法来破解库以获得自己的邪恶收益,那么你必须单独针对每个网站并破坏他们的服务器才能拥有任何影响。 这是一个很大的努力。
但现在情况并非如此,因为每个人都在使用从通用 CDN 加载的 jQuery。 当我说每个人时,我并不是指数百个网页。 我的意思是数百万个网页。 突然间,这个文件成为了我们黑幕黑客的一个非常有吸引力的目标。 如果他们可以破坏该文件,他们可以非常快速地在全球数百万个网页中运行代码。
该代码是什么并不重要。 可能是破坏页面的恶作剧,可能是窃取密码的代码,可能是挖掘加密货币的代码,也可能是在网络上跟踪您并制作营销资料的偷偷摸摸的跟踪器。 重要的是,开发人员添加到页面的无辜文件已被更改,您现在有一些恶意 JavaScript 作为您网站的一部分运行。 这是个大问题。
输入子资源完整性
SRI 不是回滚时钟并放弃使用代码的有用方式,而是一种在顶部增加简单安全级别的解决方案。 SRI 和integrity
属性所做的是确保您链接到页面的文件永远不会更改。 如果它确实改变了,那么浏览器将拒绝它。
检查代码没有改变是计算机科学中一个非常古老的问题,幸运的是它有一些非常成熟的解决方案。 SRI 在采用最简单的文件散列方面做得很好。
文件散列是获取文件并通过一种算法运行它的过程,该算法将其简化为短字符串表示,称为散列或校验和。 无需深入研究,该过程要么是可重复的,要么是可逆的,以至于如果您要给其他人一个文件以及哈希值,他们将能够运行相同的算法来检查两者是否匹配。 如果文件更改或哈希更改,则不再匹配,您知道有问题并且应该不信任该文件。
使用 SRI 时,您的网页保存哈希,服务器(CDN 或任何地方)保存文件。 浏览器下载文件,然后快速计算以确保它与integrity
属性中的哈希匹配。 如果匹配,则使用该文件,如果不匹配,则阻止该文件。
试一试
如果我今天去getbootstrap.com
获取指向 Bootstrap 版本的 CDN 链接,我会得到一个如下所示的标签:
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
您可以看到src
属性与我们习惯的一样,而integrity
属性包含我们现在知道的哈希值。
哈希实际上分为两部分。 第一个是声明要使用哪种散列算法的前缀。 在这种情况下,它是sha384
。 后面是一个破折号,然后是哈希本身,用base64
编码。
您可能熟悉base64
作为一种将内联文件(如图像)编码到页面中的方式。 这不是一个加密过程——它只是一种快速便捷的方式,以一种可以巧妙地转换为 ASCII 的方式对潜在的混乱数据进行编码。 这就是为什么它在网络上被大量使用的原因。

看到这个,浏览器会下载bootstrap.min.js
。 在执行它之前,它会对哈希进行base64
解码,然后使用sha384
哈希算法来确认哈希是否与它刚刚下载的文件匹配。 如果匹配,则执行该文件。
我可以通过将该标签放在页面中进行测试,然后在浏览器工具中翻到网络选项卡以查看文件是否已加载。

我可以看到bootstrap.min.js
(以及它需要的 jQuery 文件)已成功加载。
让我们看看如果我将哈希更新为我知道不正确的东西会发生什么。
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-SmashingMagazineIsCoolForCats" crossorigin="anonymous"></script>

如您所见,在我的页面中指定的哈希不再与文件匹配,因此文件被阻止。
在您自己的项目中使用 SRI
在 CDN 上为库提供此功能非常棒,如果您看到使用具有integrity
属性的嵌入式文件的选项,那么您绝对应该支持该选项。 但它不仅限于 CDN 上的大型项目,您可以自己将其用于自己的站点。
想象一个黑客设法访问您网站上的几个文件的场景一点也不牵强。 我想我们中的大多数人都见过一个客户、同事或朋友,他们在某个时候的 WordPress 网站受到了他们甚至没有意识到的大量令人讨厌的垃圾的影响。
SRI 也可以保护您免受此影响。 如果您为自己的文件生成完整性哈希,那么您可以让您的站点拒绝任何更改,就像对远程托管文件一样。
生成哈希
如您所料,您可以在计算机终端上运行一些命令来为文件生成哈希。 这个如何做到这一点的示例来自 MDN 子资源完整性页面:
cat FILENAME.js | openssl dgst -sha384 -binary | openssl base64 -A
这是获取FILENAME.js
的内容并将其作为输入传递给openssl
以使用sha384
创建摘要,然后将其作为输入传递给另一个openssl
命令以对结果进行base64
编码。 这不仅复杂且晦涩难懂,而且也不是您每次 JavaScript 文件更改时都希望手动执行的操作。
更有用的是,您会希望以某种方式将其集成到您网站的构建过程中,并且正如您想象的那样,那里有很多现成的选项。 确切的实现将根据您的项目而大不相同,但这里有一些构建块。
如果您使用 Gulp 构建您的站点,则 gulp-sri 将输出一个 JSON 文件,其中包含您的文件列表及其哈希值。 然后,您可以在您的站点中使用它。 例如,对于动态呈现的站点,您可以创建一个模板插件来读取该文件并在需要时将哈希添加到您的模板中。
如果您仍在使用 Gulp,但有一个静态站点(或静态生成的站点),您可能会使用 gulp-sri-hash,它实际上会运行您的 HTML 页面并修改页面以在需要的地方添加哈希,这非常方便。
如果您使用的是 Webpack,网页子资源完整性在真正的 Webpack 风格中比任何人预期的要复杂,但似乎确实有效。
对于那些使用 Handlebars 模板引擎的人来说,似乎有一些选项可供您使用,如果您的构建过程只是基本的 JavaScript,那么那里也有简单的解决方案。
如果您使用的是 WordPress 之类的 CMS,我发现了一个看起来很容易的插件,尽管我自己没有尝试过。 使用 SRI 或 Sub Resource Integrity 搜索您自己选择的平台可能会为您指明正确的方向。
你基本上想在你的 JavaScript 文件被缩小后挂钩你的散列,然后以某种方式使该散列可用于系统的任何部分输出<script>
标记。 Web 平台的一大优点是它在技术上如此多样化,但遗憾的是,我无法为您提供良好的实施说明!
其他注意事项
在本文中,我谈了很多关于 JavaScript 文件的内容,因为这确实是防御黑客攻击最有意义的地方。 SRI 也适用于 CSS,因此您可以以完全相同的方式使用它。 恶意 CSS 的风险要低得多,但破坏网站的可能性仍然存在,而且谁知道哪些浏览器错误也可能导致 CSS 无意中将您的网站暴露给黑客。 所以在那里也可以使用 SRI。
您可以做的另一件有趣的事情是使用内容安全策略来指定页面上的任何脚本(或样式)必须使用 SRI,当然 SRI 必须验证。
Content-Security-Policy: require-sri-for script;
这是一种确保始终使用 SRI 的方法,这在由多个团队成员工作的站点上可能很有用,这些成员可能会或可能不会完全了解如何做事。 再次,阅读更多有关此内容的好地方是始终很棒的 MDN 文档,用于 Subresource Integrity。
最后值得一提的是浏览器对 SRI 的支持。 现代浏览器的支持范围很广,主要的例外是 Internet Explorer。 然而,由于规范已经实现了向后兼容的方式,它可以安全地立即使用。 了解integrity
属性的浏览器将使用散列并检查完整性,而较旧的浏览器将一如既往地继续工作并继续工作。 当然,您不会在那些较旧的浏览器中获得额外的保护,但您会在提供支持的浏览器中获得。
结论
我们不仅看到了integrity
属性中那些奇怪的哈希值的作用,还看到了我们如何使用它们来防御我们网站上的某些类型的攻击。 当然,没有什么灵丹妙药可以保护我们的网站免受各种类型的攻击,但子资源完整性是链条中非常有用的工具。
利用安全漏洞通常是将多个小块排列起来。 如果 A 到位,并且您可以使 B 发生,那么 C 中的错误使 D 成为可能。 SRI 之类的浏览器功能为我们提供了一种很好的方法,可以将事情进一步捆绑,并有可能打破该链条并防止黑客获得他们想要的东西。 更重要的是,如果您可以将它集成到您的构建过程或 CMS 中,那么您应该能够设置一次然后忘记它,它不会给您带来日常的不便。
因此,我真的建议认真研究 Subresource Integrity 并尽可能在您的站点上实施它。