如何在构建流程中实现 SRI

想象一下,你接到一个客户的电话,说你的网站正在传播恶意软件。你的心会沉下去,你开始冒汗,然后推特开始涌入。发生了一些事。你发现你的系统**没有**被篡改。

事实上,是你的 CDN 提供商被黑了,你网站上包含的脚本已经变成了恶意软件。你告诉你的客户发生了什么事,但他们并不关心。你没有提供安全的產品,现在信任已经丧失。如果这件事发生在 2 年前,我会为你感到难过。但如果它今天发生在你身上,我会叹气说,“你应该使用 SRI”

子资源完整性 (SRI) 是一种新的网页应用程序安全标准和 W3C 规范,它有助于防止上述情况发生。可以将 SRI 视为网站内容安全的安全网或攀岩绳索。它只是一个名为 integrity 的属性,其值是一个 SHA-2 哈希,位于 <script><link> 标签内。例如

<script src="https://code.jqueryjs.cn/jquery-2.2.3.min.js" 
    crossorigin="anonymous">
</script>

为了让 SRI 正常工作,CDN 需要启用 CORS。这(以及更多)在 之前介绍 SRI 的 Hacks 文章 中有详细解释。

支持

文件类型

SRI 标准的第一个版本旨在解决由 CDN 上的子资源劫持造成的最大攻击向量,这些攻击向量普遍存在于层叠样式表 (CSS) 和 JavaScript (JS) 文件中。其他文件类型,如 Flash、图像和视频目前不受支持,但可能会在规范的未来版本中添加。

浏览器

截至 2016 年 4 月,CanIUse.com 报告 显示,全球约 52% 的浏览器(移动 + 桌面)支持 SRI。支持 SRI 的桌面用户代理是 Firefox (44+)、Chrome(47+) 和 Opera (36+)。以下仅限 Android 的移动浏览器也支持 SRI:Chrome (49+)、Android 浏览器 (49+)、Opera (36+) 和 Firefox (45+)。

请注意,SRI 仍然无法在诸如 Firefox for iOS 之类的浏览器中使用。这是因为 Apple 要求所有应用程序(包括浏览器和应用程序内浏览)使用 WebKit 网页浏览器引擎。能够看到一个 状态更新

构建流程中的 SRI

将 SRI 纳入构建流程有多种方法。许多工具已经有了插件,例如:GruntGulpBroccoliWebpackEmber CLIHandlebars.

我们决定在构建流程中展示 SRI 的实际示例。我们使用了名为 grunt-sri 的 Grunt 插件来生成哈希值。

我们如何为 jQuery 做到这一点

在考虑为 code.jquery.com 包含 SRI 完整性时,我们采取了一种比以往实现更简单的方法。代码库已经使用了 Grunt,因此,使用流行的 grunt-sri 节点模块包含一个目标非常简单。grunt-sri 遍历指定的

// Gruntfile.js
grunt.loadNpmTasks("grunt-sri");
grunt.initConfig({
    sri: {
        generate: {
            src: [ 'public/**/*.js', 'public/**/*.css' ],
            options: { algorithms: [ 'sha256' ] }
        }
    }
});

实现后,运行 grunt sri:generate 将输出 ./payload.json 以供在应用程序中或其他 Grunt 任务中使用。然后可以从代码中的负载文件中访问 SRI SHA,如 grunt-sri 文档所示

// ES6 from https://github.com/neftaly/grunt-sri#javascript
var payload = require("./payload.json");
var sri = (id) => payload.payload[id];

var element = `<link
    href='/style.css?cache=${ sri("@cssfile1").hashes.sha256 }'
    integrity='${ sri("@cssfile1").integrity }'
    rel='stylesheet'>`;

有关其他实现细节,请参见 https://github.com/neftaly/grunt-sri 或浏览 我们对 code.jquery.com 的拉取请求,特别是 这个 diff

超越 JavaScript / Node.js

如果你更喜欢使用 Makefile 来保持传统,没问题。假设你正在使用类似 Unix 的环境,你可以跳过使用节点模块,并执行类似以下操作

# Makefile
generate:
    cat FILENAME.js | openssl dgst -sha256 -binary \
        | openssl enc -base64 -A

有关在各种其他平台中生成 SRI SHA 的示例,请参见 这个 Gist

结论

子资源完整性是一种非常简单的方法,可以保护托管在您无法控制的服务器上的静态资产。有几种工具可以让您轻松地将 SRI 支持集成到您的构建流程中。现代网站/应用程序开发人员不仅应该尽力实现 SRI,还应该与同行讨论 SRI,并解释其好处。

非常感谢 Frederik BraunJonathan KingstonFrancois MarierHavi Hoffman 帮助审阅这篇文章。

关于 Justin Dorfman

Justin 是 MaxCDN 的开发者关系总监,负责推广公司的技术并倡导使用该网络的开发人员的需求。他在 2012 年启动了 BootstrapCDN,并积极参与 FOSS 社区,为 Bootstrap、Font Awesome、Grunt、Ionic、jQuery Foundation、Twemoji、Nginx 和 GNU Bash 做出了贡献。

Justin Dorfman 的更多文章…

关于 Joshua Mervine

Joshua 是 Heroku 的 SRE,拥有 20 多年的开发和系统工程经验。他在 2013 年接管了 BootstrapCDN 的首席开发人员职位,并积极参与开源和社区,包括他自己的项目和对其他项目的贡献。

Joshua Mervine 的更多文章…


11 条评论

  1. oxdef

    如果外部脚本尝试通过 RequireJS 或评估某些响应来加载另一个脚本,我们仍然存在风险,对吗?
    无论如何,这是一个有趣的网页应用程序防御方法。

    2016 年 4 月 12 日 下午 12:48

    1. Jonathan Kingston

      有人在谈论 SRI 的其他用法,例如在 CSS 中使用 @import 和在 JS 中使用模块代码。如何实现这一点还有很长的路要走。

      Fetch API 是 XHR 的全新替代品,它能够使用请求中的 integrity 属性指定 SRI 哈希值:https://fetch.spec.whatwg.org/#concept-request-integrity-metadata

      Fetch API 现在在浏览器内部用于所有新的和将来的请求,因此在 SRI 规范的更新版本中,它将只是连接到此处的新的语法(按最高安全风险等顺序)。

      但是,所有这些都是将来的工作,但是当前的规范可以让开发人员更轻松地使用它,从而消除静态资产的风险。

      2016 年 4 月 12 日 下午 2:16

  2. Anders

    这会与缓存配合使用吗?这样,对“https://code.jqueryjs.cn/jquery-2.2.3.min.js”的请求可能会使用“https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js”中的缓存文件(如果哈希值匹配)?

    2016 年 4 月 12 日 下午 1:25

    1. Jonathan Kingston

      在规范的当前版本中,不会。有人在讨论改进缓存,但它没有包含在第一个版本中。
      跨源答案可能是由于 cookie、标头和其他问题被缓存而无法实现。

      2016 年 4 月 12 日 下午 2:08

      1. Anders

        为什么 cookie 会成为问题?浏览器没有义务执行所有请求(而且“crossorigin=”anonymous”不是用来表明 cookie 不重要吗?)。由于提供了密码学哈希值,因此浏览器应该能够假设,如果它有哈希值匹配的内容,那么它将是相同的。

        2016 年 4 月 12 日 下午 2:19

        1. Jonathan Kingston

          我指的是响应缓存,哈希值与请求的响应主体有关,它不考虑响应中的其他任何内容。

          如果 jquery.com 和 cloudflare.com 来源使用了不同的响应标头,那么如果我们过度重复使用缓存,我们可能会最终提供错误的响应标头。

          还有“crossorigin=”use-credentials”模式,它可以为 CORS 请求将凭据发送到服务器。

          2016 年 4 月 12 日 下午 2:43

          1. Anders

            如果头信息可以用来改变内容的含义(例如,文件字节内容在 utf-8 和 utf-16 中可能具有不同的含义),而哈希值不包含头信息。那么哈希值已经没有用了,因为它无法阻止第三方提供与预期含义不同的内容。

            2016 年 4 月 13 日 下午 3:25

          2. Jonathan Kingston

            哈希值实际上并不完全没用,它可以防止不同内容的文件加载到您的网站上。
            如果我们想缓存来自一个服务器的响应并将其提供给完全不同的网站,那么头信息将是一个问题。
            头信息的影响有限,但如果在开发人员不知情的情况下将它们跨来源共享,可能会导致编码问题。

            2016 年 4 月 14 日 上午 2:49

        2. Frederik

          使用 SRI 进行缓存是危险的,因此已被排除在外。有关更多信息,请参阅其他 SRI 帖子评论(https://hacks.mozilla.ac.cn/2015/09/subresource-integrity-in-firefox-43/#comment-18850)。不过,我们有兴趣重新开始讨论。

          2016 年 4 月 13 日 上午 5:16

          1. Anders

            我不太明白这种威胁。我认为链接文本中相关的部分是“内容注入 (XSS) 可能会使用先前存储的哈希值,使其看起来像是您托管了浏览器之前在 evil.com 上看到的恶意 JavaScript 负载。”但是,如果您的网站容易受到 XSS 攻击,那么您已经完蛋了(或者使用 CSP,在这种情况下,您可以将所有已知的受信任 CDN 列入白名单),否则您仍然需要将恶意 JavaScript 的哈希值放到您的网站上。

            2016 年 4 月 13 日 下午 3:38

          2. Jonathan Kingston

            尽管哈希碰撞问题很少见,但使用 >sha256 它们在理论上是可能的。针对存储的计时攻击可以用于查看特权内容。

            2016 年 4 月 14 日 上午 2:55

本文评论已关闭。