浏览器的灵魂拷问:你使用的 script 脚本真的安全吗?

143 阅读6分钟

上一期对script标签进行了介绍,同时也引出了关于跨域资源访问安全的一些问题。

# 浏览器的灵魂拷问:你真的会用script标签吗?

今天,我们就基于这些安全问题,一起来探讨一下。

跨域资源访问

首先我们必须要知道,浏览器的同源策略,它限制了不同源站点之间的交互。

可能大多数人只联想到了AJAX请求,其实同源策略也作用于DOM与本地存储的数据,比如:前端不同源时跨页面通信、与iframe子页面通信等。

然而scriptlinkimgaudiovideo等标签不受同源策略的限制,这是因为早期浏览器为了让Web应用有能力使用第三方的公共资源文件(CDN)而制定的必要手段。也许在今天来看,这种手段可能存在一定的“缺陷”。

crossorigin属性就是浏览器让这些不受同源策略控制的标签,也走CORS这一套方案

如果,一开始就让script默认走CORS这一套,让服务器来决定是否允许跨域访问,是不是就不需要crossorigin这个属性存在了?说到底这些都是浏览器不断进化的必然因素,包括任何历史产物都是一样需要不断完善与改进。

script标签有了CORS的能力,那么跨域资源就能够由服务端来决定允许哪些站点访问。

那么,深思考一下,让script拥有CORS能力后,对第三方公共资源的访问到底带来了哪些实质性的安全呢?

假设服务器对公共CDN资源Vue框架启用了CORS,并且只允许a.com访问。那么:

  1. 不使用crossorigin属性时,所有网站都可以正常访问;
  2. 如果使用了crossorigin属性,只允许A站点访问。

如果我是A站点,可以选择用与不用crossorigin属性都可以;如果我不是A站点,我直接不加crossorigin属性也能正常使用Vue框架

所以我觉得:crossorigin更像是开启了一道标准,要实现哪种具体的安全能力,还需要结合其他方案,也为以后的安全演化提前制定了标准。

以上说的都是crossorigin值为anonymous的情况;当crossorigin的值为use-credentials时,它指定了资源请求需要携带用户凭据(如Cookies、HTTP认证信息),一般只用于企业内部的私有脚本。

安全方案

我们来看一下关于script标签相关的具体的安全方案。

1. 完整性校验

试想一下,如果我们加载的脚本文件被攻击者修改过,加入了恶意代码。这对浏览器来说,它是不知道的。

那如何来保证我们脚本代码的可靠性呢?确保它是官方的、原始的那份资源文件呢?

这就需要用到了integrity这个属性了

integrity的值包含两个部分:前缀 + 散列值

  • 前缀:指定使用的加密算法,如:sha256sha384sha512
  • 散列值:对资源文件进行指定算法加密后,再进行Base64编码。
# 使用sha384算法,计算a.js文件的散列值
cat a.js | openssl dgst -sha384 -binary | openssl base64 -A
# 输出 kkkrsEK4/JSqoGQd4LYR8Da5q1+kS/4Uox9rIrSe1C8dtlpLXapr4J6x1K+pUOmN

我们在使用第三方资源文件时,平台通常都会提供脚本文件的integrity值。浏览器会在执行脚本代码之前,先对其进行验证。如果不匹配,就说明文件可能被篡改过,代码就不会被执行。

<script src="http://localhost:3000/a.js" crossorigin="anonymous" integrity="sha384-kkkrsEK4/JSqoGQd4LYR8Da5q1+kS/4Uox9rIrSe1C8dtlpLXapr4J6x1K+pUOmN"></script>

启用integrity完整性校验能力后,如果是跨域的资源,就必须使用crossorigin属性。

2. CSP内容安全策略

CSP是在同源策略之外,另一套“终极”的Web应用防范机制,它主要的目的是减少 XSS 攻击

它的实现原理是通过指定白名单来允许哪些资源可以被执行。通常有两种指定方式:

  • 响应头:在html文档请求的响应头中设置;
  • meta标签:直接在html文档中通过meta标签设置。
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-inline' *.codingmo.com 127.0.0.1:3000">

content中配置的内容通常叫做策略,所以我们要做的就是如何编写策略

策略可以配置多组,中间用;隔开。default-src对应的是策略指令部分,它是默认的策略,用来兜底的。

具体的常用策略指令还有:

  • script-src:指定脚本策略;
  • style-src:样式表策略;
  • img-srcfont-srcmedia-srcrequire-cors等。

策略指令后面就是具体的白名单,中间使用空格隔开。

  • self:只允许与当前站点同源的资源;
  • unsafe-line:允许内联脚本或样式(通常不使用,防XSS);
  • none:禁止任何来源;
  • 也支持直接写通配符(**.a.com)、具体域名、URL、协议(https:data:);
  • 还支持上面提到的integrity值,和nonce-xxx随机数。

这一套策略很多很全面,我就不一一列举了。

其中nonce随机数大家可能见到过,就是可以在script标签上设置一个随机值nonce="abcabc",然后在CSP策略上写上script-src nonce-abcabc。如果随机值匹配,脚本内容才会加载或执行。

<meta http-equiv="Content-Security-Policy" content="script-src nonce-abcabc">
<!-- nonce随机值匹配才会被加载 -->
<script nonce="abcabc" src="a.js"></script>

那么,CSP与crossorigin有没有关系呢?

其实,它们两个之间没有相互依赖的关系,只有当CSP策略配置了require-cors指令时,它会强制客户端使用crossorigin属性来启用CORS能力。但是如果服务端没有启用CORS,require-cors指令也会失效。

总结

因此,crossorigin属性是为了让那些不受同源策略限制的标签也拥有CORS的能力,但又为了向前兼容,不使用crossorigin属性直接加载第三方脚本资源也不会有什么问题。可能就是失去了一些能力,比如:无法捕获详细错误信息、无法使用一些敏感API等。更重要的场景是配合integrity属性验证跨域资源的完整性

而CSP内容安全策略是另一套独立的安全机制,它通过白名单来限制哪些资源可以被加载或执行,更全面也更极端的限制了前端的资源文件。也正是因为它的严苛,所以现在一般站点都没有使用或轻度使用,属于是伤敌一千自损八百的存在。

当然,所有的技术都是不断演化的,也许有一天,在WebpackVite这类打包工具的推进下,CSP也能够得到大范围使用。

谁又知道呢?说说你的见解吧!