上一期对script标签进行了介绍,同时也引出了关于跨域资源访问安全的一些问题。
今天,我们就基于这些安全问题,一起来探讨一下。
跨域资源访问
首先我们必须要知道,浏览器的同源策略,它限制了不同源站点之间的交互。
可能大多数人只联想到了AJAX请求,其实同源策略也作用于DOM与本地存储的数据,比如:前端不同源时跨页面通信、与
iframe子页面通信等。
然而script、link、img、audio、video等标签不受同源策略的限制,这是因为早期浏览器为了让Web应用有能力使用第三方的公共资源文件(CDN)而制定的必要手段。也许在今天来看,这种手段可能存在一定的“缺陷”。
而crossorigin属性就是浏览器让这些不受同源策略控制的标签,也走CORS这一套方案。
如果,一开始就让
script默认走CORS这一套,让服务器来决定是否允许跨域访问,是不是就不需要crossorigin这个属性存在了?说到底这些都是浏览器不断进化的必然因素,包括任何历史产物都是一样需要不断完善与改进。
script标签有了CORS的能力,那么跨域资源就能够由服务端来决定允许哪些站点访问。
那么,深思考一下,让
script拥有CORS能力后,对第三方公共资源的访问到底带来了哪些实质性的安全呢?
假设服务器对公共CDN资源Vue框架启用了CORS,并且只允许a.com访问。那么:
- 不使用
crossorigin属性时,所有网站都可以正常访问; - 如果使用了
crossorigin属性,只允许A站点访问。
如果我是A站点,可以选择用与不用crossorigin属性都可以;如果我不是A站点,我直接不加crossorigin属性也能正常使用Vue框架。
所以我觉得:crossorigin更像是开启了一道标准,要实现哪种具体的安全能力,还需要结合其他方案,也为以后的安全演化提前制定了标准。
以上说的都是crossorigin值为anonymous的情况;当crossorigin的值为use-credentials时,它指定了资源请求需要携带用户凭据(如Cookies、HTTP认证信息),一般只用于企业内部的私有脚本。
安全方案
我们来看一下关于script标签相关的具体的安全方案。
1. 完整性校验
试想一下,如果我们加载的脚本文件被攻击者修改过,加入了恶意代码。这对浏览器来说,它是不知道的。
那如何来保证我们脚本代码的可靠性呢?确保它是官方的、原始的那份资源文件呢?
这就需要用到了integrity这个属性了。
integrity的值包含两个部分:前缀 + 散列值。
- 前缀:指定使用的加密算法,如:
sha256、sha384和sha512。 - 散列值:对资源文件进行指定算法加密后,再进行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-src、font-src、media-src、require-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内容安全策略是另一套独立的安全机制,它通过白名单来限制哪些资源可以被加载或执行,更全面也更极端的限制了前端的资源文件。也正是因为它的严苛,所以现在一般站点都没有使用或轻度使用,属于是伤敌一千自损八百的存在。
当然,所有的技术都是不断演化的,也许有一天,在Webpack、Vite这类打包工具的推进下,CSP也能够得到大范围使用。
谁又知道呢?说说你的见解吧!