【干货】加强 web 静态资源安全方法之SRI

我们通常会用CSP加强站点JS资源的执行限制,有效降低XSS攻击;我们通过HTTPS链接加密资源,减少站点资源劫持风险等等大量的前端安全方案。但你可能还没听说 Subresource Integrity (SRI) 子资源完整性校验。

本文将带你了解SRI是什么,能解决哪些安全风险,如何快速接入。同时,使用它又会带来哪些问题,以及浏览器的支持情况如何。

什么是SRI ?

SRI 是 Subresource Integrity 的缩写,可翻译为:子资源完整性,它也是由 Web 应用安全工作组(Web Application Security Working Group)发布。

截止2018年11月13日,已经进入建议阶段。提议地址:http://www.w3.org/TR/SRI/

通过对 HTML 中外链 JS/CSS 进行摘要签名,保证浏览器下载的资源的完整性,防止资源被篡改。

解决什么问题?

Web 性能优化中为了让静态资源尽快下载完,通常我们将 JS/CSS/Image 等静态资源部署在 CDN 服务器。CDN 服务提供商通过分布在各地的节点,让用户从最近的节点下载资源,大幅提升下载速度。但是 CDN 的安全性一直是一个风险点,让请求从第三方服务器经过,由第三方响应,安全性不可控,一旦 CDN 出现安全问题,就导致我们的站点也出现安全风险。

我们知道 CSP(Content Security Policy) 的机制可以降低 XSS 风险。但我们存储在 CDN 的内容被篡改而导致的 XSS,CSP 并不能防范,因为网站所使用的 CDN 域名,肯定在 CSP 白名单之中。因此 SRI 就应运而生了,通过它避免用户加载了第三方服务器被修改的资源。

使用方法

使用 SRI 之前你的 html 引用 JS 方式如下:

<script src="https://7.url/fudao/pc/vender-xxx.js" crossorigin="anonymous"></script>

使用 SRI 之后,变成这样了:

<script src="https://7.url/fudao/pc/vender-xxx.js"
        integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
        crossorigin="anonymous"></script>

使用前后其实就多了一个属性 integrity,integrity 值分成两个部分,第一部分指定哈希值的生成算法(目前支持 sha256、sha384 及 sha512),第二部分是经过 base64 编码的实际哈希值,两者之间通过一个短横(-)分割。

启用 SRI 策略后,浏览器会对资源进行 CORS 校验,这就要求被请求的资源必须同域,或配置 Access-Control-Allow-Origin 响应头和添加crossorigin="anonymous" 这个属性。

浏览器根据以下步骤处理 SRI:

1. 当浏览器在 <script>或者 <link> 标签中遇到 integrity 属性之后,会在执行 JS或者应用 style 之前,对比所加载文件的哈希值和期望的哈希值。

2. JS 或者 style 的哈希值和期望的不一致时,浏览器必须拒绝执行 JS 或者应用style,并且会触发 error 事件返回一个网络错误。

如何快速接入?

如果你使用了 webpack 构建你的项目,推荐你使用 webpack-subresource-integrity插件(https://www.npmjs.com/package/webpack-subresource-integrity)自动地处理SRI。

⚠️上面的例子只是 JS ,SRI 也支持 CSS 安全校验

CSP 及 SRI 联合使用

你可以根据内容安全策略来配置你的服务器使得指定类型的文件遵守 SRI。这是通过在 CSP 头部添加 require-sri-for 指令实现的:

Content-Security-Policy: require-sri-for script;

这条指令规定了所有 JavaScript 都要有 integrity 属性,且通过验证才能被加载。

你也可以指定所有样式表也要通过 SRI 验证:

Content-Security-Policy: require-sri-for style;

你也可以对两者都加上验证。

启用 SRI 后会出现什么问题?怎么解决?

显而易见,当资源验证不通过,也就是用户下载的资源被劫持了,就会导致用户直接不可用,因为浏览器会触发错事件,并且丢弃下载的资源。这可能导致整个页面都不可用了!

那么这种情况怎么处理?

针对 CDN 资源失败的情况,我们可以通过添加额外的部署站点重试,例如:直接让用户从主域名下载资源,具体实现方式如下:

同步JS资源: 失败后我们直接使用 document.write 继续加载主域资源,这里可也通过构建实现。

<script src="https://7.url/fudao/pc/vender-xxx.js"
        integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7"
        crossorigin="anonymous"></script>
<script>
    if(!window.IMWEB_SRI["vender-xxx"]){
    // 上报
      document.write(
      '<script 
          src="https://fudao.qq.com/pc/vender-xxx.js" 
          integrity="sha384-Li9vy3DqF8tnTXuiaAJuML3ky+er10rcgNR/VqsVpcw+ThHmYcwiB1pbOxEbzJr7" 
          crossorigin="anonymous" 
          onerror="report()"
          onerror="report()" />'
      );
     }
</script>

上面的代码你可能一些疑问?️,为什么做if(!window.IMWEB_SRI["vender-xxx"])判断后直接加载主域名资源?

实际上我们在构建阶段做了处理,在每个 JS 文件里面中插入了一段代码:

window.IMWEB_SRI=window.IMWEB_SRI||{};
// 当资源加载成功后,全局就有这个变量,我们就能够判断是否需要重试主域资源
window.IMWEB_SRI["vender-xxx"]=true;

异步 JS 资源:针对站点的异步加载资源就比较容易控制了, 可以有如下实现(基于 webpack4 实现):

function scriptLoader(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.onerror = reject;
    script.onload = resolve;
    script.crossorigin = 'anonymous';
    script.src = src;
    document.body.appendChild(script);
  })
}

import('xxx.js').catch((error)=>{
    // 上报重试
    const src = "//fudao.qq.com/pc/"+ error.request.split('/').pop();
    return scriptLoader(src)
    .catch(err=>{
        //重试失败
        return  Promise.reject(err);
    })
})

同步 link:直接在每个 link 上面添加异常捕获,得到 error 事件后,重新加载主域名资源,具体实现可以参考

<script>
function styleRetry() {
  // 上报重试
  var linkTag = document.createElement('link');
  linkTag.rel = 'stylesheet';
  linkTag.type = 'text/css';
  linkTag.onload = resolve;
  linkTag.onerror = function(event) {
    // 上报重试失败
  };
  linkTag.href ="//fudao.qq.com/pc/"+this.href.split('/').pop();
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(linkTag);
};
</script>
<link 
    href="//7.url.cn/fudao/pc/vendor_xxx.css" 
    rel="stylesheet"
    onerror="styleRetry()"
/>

异步加载的 CSS:处理方式和 JS 加一样即可。

现状

github 很早以前都启用了 SRI 策略了。

看这里:https://githubengineering.com/subresource-integrity/

你可以直接打开 github 网站查看源代码,就能好发现其踪迹。

caniuse (https://caniuse.com/#search=sri)

从上图看出,主流浏览器基本都支持了。小伙伴赶紧加入你的项目中去吧。

原文作者:腾讯高级工程师刘华 来源:腾讯内部KM论坛

原文发布于微信公众号 - 腾讯NEXT学院(Next_Academy)

原文发表时间:2018-11-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏农夫安全

某服务器安全溯源审计报告

流量关联分析 通过查看服务器连接的程序发现有外网ip 23.33.178.8和91.121.2.76两个,查看到clock-applet一般为程序自带的文件,而...

3486
来自专栏jeremy的技术点滴

使用logrotate进行日志分割及滚动处理

3955
来自专栏nice_每一天

分布式任务调度平台XXL-JOB

XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

3423
来自专栏魏艾斯博客www.vpsss.net

Linux VPS 禁用 root 账号 保障系统安全

黑客入侵从账号入手,一般用软件扫描弱口令或者猜测登陆账号,为了保障 linux 系统安全,我们可以把默认 root 账号改成只有自己知道的账号,这样从入口安全方...

2533
来自专栏我的安全视界观

【应急响应】redis未授权访问致远程植入挖矿脚本(攻击篇)

4126
来自专栏程序工场

100 个较全面的 IT 热门编程开发视频教程

2155
来自专栏源码之家

最新淘客AppKey申请教程

4042
来自专栏我爱编程

关于 Really Simple SSL 插件的使用笔记

首先,在插件 Really Simple SSL 的默认配置里,插件本身是自带301重导向到 https 设定的。 其次也默认内部的 WordPress 30...

1073
来自专栏FreeBuf

WinRAR 0day漏洞 (附利用过程)

英国安全机构Mohammad Reza Espargham的漏洞实验室发现,流行压缩工具WinRAR 5.21最新版里存在一个安全漏洞,目前该漏洞还属于零日漏洞...

7338
来自专栏腾讯云存储团队

使用 s3browser 管理腾讯云 COS 存储桶文件

腾讯云 COS 有提供一个桌面工具 cosbrowser,可以可视化管理 COS 存储桶文件,支持 Windows、macOS。

4716

扫码关注云+社区