提高资源的安全性 – SRI 与 CSP

TODO

《前端资源加载失败优化》 文章中,我们聊到了前端资源加载失败的监控方式,以及资源加载失败时的优化方案。通过对加载失败的资源更换域名动态重新加载、同时确保最终代码正常的执行顺序,从而有效地减少了因为资源加载失败导致的网页异常。到此,资源文件成功加载了!但加载到的是否就是正常的资源呢?如何应对加载过程中被半路劫持?又该如何监控?是否能做更多的防护措施呢?本文将逐步进行解答分析。

劫持

流量劫持在 Web 项目中是一个老生常谈的话题了,常见的劫持方式是往 JS 代码文件中注入一段脚本,从而实现一段广告“完美”植入。

而当注入的位置稍有偏差,导致代码执行异常,页面将完全不可用。

上面现象在使用以明文传输、不带加密的 HTTP 协议中经常遇到,毕竟流量在传输过程中裸奔,劫持轻而易举。HTTPS 应运而生,通过证书加密等方式保证了传输过程中的数据完整性,开启 HTTPS 后,这类劫持问题也基本不复存在了。

原本以为就此安稳一生,直到有一天,钟声再次响起,有用户反馈遇到了广告、还有打开白屏。通过具体的定位分析,最终发现返回的 CDN 文件只剩一半内容。难道 HTTPS 协议被破译了吗?其实并不是。HTTPS 是可以有效应对流量劫持的问题,然而很多提供 HTTPS 的 CDN 服务在回源的时候采用的 HTTP 协议,流量劫持便有机可乘,那么开启全链路的 HTTPS 是否就万无一失了呢?大部分情况确实如此。但如果遇到 CDN 服务入侵、源头污染,或者用户信任了异常证书导致的中间人劫持,千里之堤,溃于蚁穴,防御之门被摧毁后,便是任人宰割。为了尽可能的安全,或许我们可以再加一道防线,那就是 SRI。

SRI(Subresource Integrity)

SRI 是用来校验资源是否完整的安全方案。通过为页面引用的资源指定信息摘要,当资源被劫持篡改内容时,浏览器校验信息摘要不匹配,将会拒绝代码执行并抛出加载异常,保证加载资源的完整性。

使用 SRI

使用 SRI 只需要给页面标签添加 integrity 属性,属性值为签名算法(sha256、sha384、sha512)和摘要签名内容组成,中间用 – 分隔。

function getIntegrity() {
    const hashFuncName = 'sha256';
    const hash = crypto
        .createHash(hashFuncName)
        .update(source, 'utf8')
        .digest('base64');
    return hashFuncName + '-' + hash;
}

我们也可以使用 webpack-subresource-integrity 实现 integrity 的添加过程,保持对开发者透明。

开启 SRI 后,浏览器会对相关资源进行 CORS 校验,被加载的资源要么在同域下,要么得满足 CORS 要求(配置方式可查看 脚本错误量极致优化-监控上报与Script error 中**跨源资源共享机制( CORS ) **章节)。

就此,当资源内容被劫持篡改,浏览器校验签名不匹配时,将使得异常资源不被执行,并触发加载失败。进而能够被资源加载失败所监控到,最终可以通过切换 CDN 域名或进行主域名加载重试,从而加载到正确资源,避免资源被劫持篡改内容后注入广告等情况。

监控及重加载的具体方式可见 《前端资源加载失败优化》 ,加载失败的原因有很多,那么又该如何区分是由 SRI 机制触发的呢?可以采用下面思路:

  1. 当加载失败时,切换域名重加载到正确资源;
  2. 重新请求原加载失败的 URL 和最终正常加载的 URL,抽样对比两份内容是否存在差异,如存在差异,则存在内容被篡改,属于 SRI 触发的加载失败。

最终再搭配上报和告警机制,当遇到劫持问题时,及时获知。

“此处可以加上告警的图”

知道是遇到劫持问题之后,又该如何处理呢?向运营商客服投诉或工信部投诉或许是个办法。不过在此之前我们也可以先主动触发刷新 CDN 节点缓存的资源,避免被劫持污染的资源被继续访问。而对于已经缓存到异常资源的用户,特别是在不方便强制刷新页面的环境下,下次访问会先接着访问异常缓存,对此,修改文件 hash、重新发布,强制刷新资源倒也是一种解决方式。

总结

SRI 能够保证加载的资源内容是完整性,但也并非银弹,理论上如果连同主页面被劫持,去掉资源的信息摘要也就失去了 SRI 的保护机制,好在实际上还没遇到过这种情况。当然,安全之路本身就充满着荆棘与挑战,多加一层防弹衣,增加安全指数总归不会错。

资源内容完整(不被篡改)加载下来了,但加载的是否都是我们需要的资源呢?对此,我们可以启动 CSP(Content Security Policy)机制来保证加载的是需要的资源文件、执行的是正常的脚本。一方面通过制定 CSP 的外链白名单机制,限制了不可信域名的资源加载,另一方面通过开启 nonce 模式,确保执行的是正常的内联脚本。具体方式可以查看[ 《XSS终结者-CSP理论与实践》《Csp Nonce – 守护你的 inline Script》 这两篇文章。

以上为本文所有内容,