JS `Subresource Integrity (SRI)` 原理:防止第三方脚本篡改

咳咳,各位观众老爷,早上好/下午好/晚上好! 今天咱们来聊聊一个前端安全领域的小可爱,但作用却无比巨大的家伙——Subresource Integrity,简称SRI。 别看名字高大上,其实它就是用来保护你网站上引用的那些第三方脚本,防止它们被坏人篡改,从而保证你网站的安全。

一、啥是Subresource Integrity (SRI)?

简单来说,SRI就像是给每个你引用的第三方脚本文件都盖了个防伪戳。 你在引用的时候,除了指定脚本的URL,还要提供这个脚本的“指纹”(也就是哈希值)。浏览器在加载脚本的时候,会先检查脚本的“指纹”是不是和你提供的“防伪戳”对得上。如果对不上,说明这个脚本可能被篡改了,浏览器就会拒绝执行它,从而保护你的网站免受恶意代码的侵害。

二、为什么需要SRI?

想象一下,你辛辛苦苦搭建了一个网站,一切都棒棒哒。 但是,你的网站用了一些第三方提供的JavaScript库,比如jQuery、Bootstrap啥的。 这些库放在CDN上,方便快捷,还能加速你的网站。 但是,如果CDN被黑了,或者被恶意人员控制了,他们就可以修改这些JavaScript库的内容,插入恶意代码。

如果你的网站没有使用SRI,那么浏览器就会直接加载这些被篡改的脚本,你的网站就可能被劫持,用户的隐私数据可能被窃取,甚至整个网站都被搞垮。 这可不是闹着玩的!

有了SRI,情况就不一样了。 即使CDN被黑了,恶意人员修改了JavaScript库的内容,浏览器在加载的时候会发现脚本的“指纹”和预期的不一致,就会拒绝执行这个脚本。 这样,你的网站就能避免被恶意代码侵害。

三、SRI的工作原理

SRI的核心就是使用哈希算法,对脚本文件进行计算,生成一个唯一的哈希值。 这个哈希值就像是脚本的身份证,可以用来验证脚本的完整性。

  1. 生成哈希值: 使用SHA-256、SHA-384或SHA-512等哈希算法,对脚本文件进行计算,生成哈希值。

  2. 添加到<script>标签: 将生成的哈希值添加到<script>标签的integrity属性中。

  3. 浏览器验证: 浏览器在加载脚本时,会先计算脚本文件的哈希值,然后与integrity属性中提供的哈希值进行比较。 如果两个哈希值一致,说明脚本文件没有被篡改,浏览器会正常执行它。 如果两个哈希值不一致,说明脚本文件可能被篡改了,浏览器会拒绝执行它。

四、如何使用SRI?

使用SRI非常简单,只需要以下几个步骤:

  1. 获取脚本文件的哈希值: 可以使用各种工具来生成脚本文件的哈希值,比如OpenSSL、Subresource Integrity Hash Generator等。

    • 使用OpenSSL:
      在命令行中执行以下命令:

      openssl dgst -sha384 -binary your-script.js | openssl base64 -A

      your-script.js替换成你的脚本文件名。

      这个命令会先使用SHA-384算法计算脚本文件的哈希值,然后将哈希值进行Base64编码,最后输出结果。

    • 在线工具:
      网上有很多在线的Subresource Integrity Hash Generator工具,你只需要将脚本文件的内容复制到工具中,就可以生成哈希值。

  2. 将哈希值添加到<script>标签: 将生成的哈希值添加到<script>标签的integrity属性中,并指定哈希算法。

    <script
      src="https://cdn.example.com/your-script.js"
      integrity="sha384-YOUR_HASH_VALUE"
      crossorigin="anonymous"
    ></script>

    https://cdn.example.com/your-script.js替换成你的脚本URL,将sha384-YOUR_HASH_VALUE替换成你生成的哈希值。

    注意:

    • integrity属性的值必须以哈希算法名称开头,比如sha256-sha384-sha512-
    • crossorigin属性是必须的,它的值必须设置为anonymoususe-credentials
      • anonymous:表示不发送任何身份验证信息,比如Cookie。
      • use-credentials:表示发送身份验证信息,比如Cookie。 只有在你的CDN服务器允许跨域请求时,才能使用use-credentials。 一般情况下,使用anonymous就可以了。

五、SRI的最佳实践

  1. 使用多种哈希算法: 为了提高安全性,可以使用多种哈希算法,比如SHA-256、SHA-384和SHA-512。 这样,即使其中一种哈希算法被破解了,其他的哈希算法仍然可以保护你的网站。

    <script
      src="https://cdn.example.com/your-script.js"
      integrity="sha256-HASH_SHA256 sha384-HASH_SHA384 sha512-HASH_SHA512"
      crossorigin="anonymous"
    ></script>
  2. 定期更新哈希值: 如果你使用的第三方脚本更新了,那么你需要重新生成哈希值,并更新<script>标签中的integrity属性。

  3. 监控SRI错误: 浏览器会在控制台中输出SRI错误,你可以监控这些错误,及时发现问题。

  4. 备份方案: 如果SRI验证失败,浏览器会拒绝执行脚本。 为了保证网站的正常运行,你可以提供一个备份方案,比如加载本地的脚本文件。

    <script
      src="https://cdn.example.com/your-script.js"
      integrity="sha384-YOUR_HASH_VALUE"
      crossorigin="anonymous"
    ></script>
    <script>
      // 如果SRI验证失败,会执行这里的代码
      var script = document.createElement('script');
      script.src = 'local-script.js';
      document.head.appendChild(script);
    </script>
  5. 使用工具自动化SRI: 手动生成和更新哈希值非常麻烦,可以使用一些工具来自动化这个过程,比如grunt-sri、webpack-subresource-integrity等。

六、代码示例

假设我们有一个名为app.js的脚本文件,内容如下:

console.log('Hello, SRI!');
  1. 生成哈希值:

    使用OpenSSL:

    openssl dgst -sha384 -binary app.js | openssl base64 -A

    假设生成的哈希值为:i7wzYkQ/Jz4U5v5b7Z9B4n+Wv7J8N6x2Y6z7A8B9C0=

  2. 添加到<script>标签:

    <!DOCTYPE html>
    <html>
      <head>
        <title>SRI Example</title>
      </head>
      <body>
        <h1>Hello, World!</h1>
        <script
          src="app.js"
          integrity="sha384-i7wzYkQ/Jz4U5v5b7Z9B4n+Wv7J8N6x2Y6z7A8B9C0="
          crossorigin="anonymous"
        ></script>
      </body>
    </html>

    在这个例子中,我们将app.js放在了本地服务器上,并指定了它的哈希值。

七、SRI的优点和缺点

优点:

  • 提高安全性: 防止第三方脚本被篡改,保护网站免受恶意代码侵害。
  • 简单易用: 只需要添加一个integrity属性就可以使用SRI。
  • 浏览器原生支持: 不需要安装任何插件或库。

缺点:

  • 增加维护成本: 需要定期更新哈希值。
  • 可能导致性能问题: 浏览器需要计算哈希值,可能会稍微增加加载时间。
  • 兼容性问题: 虽然现代浏览器都支持SRI,但是一些老旧的浏览器可能不支持。

八、SRI与CSP(Content Security Policy)

SRI和CSP都是用来提高网站安全性的技术,但是它们的作用不同。

  • SRI: 用来验证第三方脚本的完整性,防止脚本被篡改。
  • CSP: 用来限制浏览器可以加载哪些资源,防止XSS攻击。

可以将SRI和CSP结合使用,以提高网站的整体安全性。

九、总结

SRI是一个简单而强大的安全特性,可以有效地保护你的网站免受恶意代码的侵害。 虽然它有一些缺点,但是总体来说,使用SRI是值得的。 尤其是在你使用了大量的第三方脚本的情况下,SRI可以为你提供额外的安全保障。

十、彩蛋:SRI 的一些不常见的用法

  • SRI for CSS: 是的,你没听错,SRI 也可以用于CSS文件! 和JS一样,你可以计算CSS文件的哈希值,然后添加到<link>标签中。

    <link
      rel="stylesheet"
      href="style.css"
      integrity="sha384-ANOTHER_HASH_VALUE"
      crossorigin="anonymous"
    />
  • 动态生成 SRI: 在一些高级场景下,你可能需要在运行时动态生成SRI。 例如,你可能需要根据用户的配置来加载不同的脚本,而这些脚本的哈希值需要在运行时才能确定。 这需要一些JavaScript技巧,但绝对可行。

    // 假设 scriptContent 是你动态获取的脚本内容
    function generateSRIHash(scriptContent) {
      const encoder = new TextEncoder();
      const data = encoder.encode(scriptContent);
      return crypto.subtle.digest('SHA-384', data)
        .then(buffer => {
          const hashArray = Array.from(new Uint8Array(buffer));
          const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
          return 'sha384-' + btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); // 转换成 base64
        });
    }
    
    // 使用示例
    fetch('dynamic-script.js') // 假设从服务器获取动态脚本
      .then(response => response.text())
      .then(scriptContent => {
        generateSRIHash(scriptContent)
          .then(sriHash => {
            const script = document.createElement('script');
            script.src = 'dynamic-script.js';
            script.integrity = sriHash;
            script.crossOrigin = 'anonymous';
            document.head.appendChild(script);
          });
      });
    

    注意: 这段代码需要在支持 crypto.subtle API 的环境中运行(比如现代浏览器)。 而且,动态生成SRI通常需要仔细考虑安全风险,确保你的代码没有引入新的漏洞。

  • SRI与模块化: 如果你使用模块化工具(例如Webpack、Parcel)来构建你的前端应用,这些工具通常会提供SRI的插件或配置选项,可以自动生成和管理SRI哈希值。 这大大简化了SRI的使用过程。

总而言之,SRI是一个非常实用的安全特性,可以帮助你保护你的网站免受恶意攻击。 虽然它不是万能的,但它可以为你提供额外的安全保障,让你更加安心。

好了,今天的讲座就到这里。 感谢各位的观看,希望对大家有所帮助! 如果有什么问题,欢迎随时提问。 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注