由一个报错引发的浏览器跨域隔离探索

背景

众所周知,我们的浏览器都遵循同源策略,同源策略限制了网站的一些跨域行为。但有一些情况是例外的,例如网站中通过

不久前有一些用户反馈,他的CDN资源图片在他的站点中无法加载,具体表现如下图。后来发现是由于他的站点中开启了跨域隔离,导致跨域资源无法加载,需要将他的CDN资源设置Cross-Origin-Resource-Policy(CORP)响应头才能正常加载。

由于用户对他的站点开启了Cross-Origin-Embedder-Policy(COEP)策略,导致嵌入的所有跨域资源必须显式开启Cross-Origin-Resource-Policy(CORP)否则将无法访问。那么用户为什么要对他的站点开启Cross-Origin-Embedder-Policy(COEP)策略呢?这是因为用户在项目中需要用到SharedArrayBuffer这个JS API,如果不开启COEP这个API将不可用。那么SharedArrayBuffer与这些策略又有什么关系。接下来带着这些问题对浏览器跨域隔离策略相关内容进行介绍。

Spectre漏洞

SharedArrayBuffer

ES8引入了SharedArrayBuffer,通过共享内存来提升workers之间或者worker和主线程之间的消息传递速度。先看一个worker的例子:

//
// 主线程
const w = new Worker('worker.js');
w.postMessage('hi');
w.onmessage = function (ev) {console.log(ev.data);
} 
// worker.js
onmessage = function (ev) {console.log(ev.data);postMessage('hello');
} 

主线程新建了一个 Worker 线程。该线程与主线程之间会有一个通信渠道,主线程和Worker线程都是通过postMessage向对方发消息,同时通过message事件监听对方的回应。线程之间的数据交换可以是各种格式,不仅仅是字符串,也可以是二进制数据。这种交换采用的是复制机制,即一个线程将需要分享的数据复制一份,通过postMessage方法交给另一个线程。消息是拷贝之后,经过序列化之后进行传输的。在解析的时候又会进行反序列化,这也降低了消息传输的效率。如果数据量比较大,这种通信的效率显然比较低。

为了解决这个问题,引入了Shared Memory的概念。我们可以通过SharedArrayBuffer来创建Shared Memory,允许 Worker 线程与主线程共享同一块内存。SharedArrayBuffer的 API 与ArrayBuffer一模一样,例如本身是无法读写的,必须在上面建立视图,然后通过视图读写,唯一的区别是后者无法共享数据。可以看一个示例:

// 主线程
// 新建 1KB 共享内存
const sharedBuffer = new SharedArrayBuffer(1024);
// 主线程将共享内存的地址发送出去
w.postMessage(sharedBuffer);
// 在共享内存上建立视图,供写入数据
const sharedArray = new Int32Array(sharedBuffer);
sharedArray[0] = 123; 
//
// Worker 线程
onmessage = function (ev) {// 主线程共享的数据,就是 1KB 的共享内存const sharedBuffer = ev.data;// 在共享内存上建立视图,方便读写const sharedArray = new Int32Array(sharedBuffer);console.log(sharedArray[0]);// 123
}; 

复制机制接收 SharedArrayBuffer 对象,或被映射到一个新的 SharedArrayBuffer 对象上的 TypedArrays 对象。在这两种情况下,这个新的 SharedArrayBuffer 对象会被传递到目标Worker的接收函数上,从而在目标Worker产生一个新的私有 SharedArrayBuffer 对象。但是,这两个 SharedArrayBuffer 对象指向的共享数据块其实是同一个。

SharedArrayBuffer 是用来和线程之间进行数据交换访问的高效方法,被大量应用,例如WebAssembly 使用 Worker 模拟了多线程,使用了 SharedArrayBuffer 做数据共享访问。

浏览器上下文组

浏览器上下文组(Browsing Context Group)是一组共享相同上下文的 tab、window或iframe。例如,如果网站a.example打开弹出窗口b.example,则打开器窗口和弹出窗口共享相同的浏览上下文,并且它们可以通过 DOM API相互访问,例如 window.opener.postMessage()。一般来说,我们认为同一个上下文组中的内容都处于同一个进程。

众所周知,浏览器是基于同源策略构建的,该策略限制了网站访问跨域资源的方式,但是有些情况是例外的:

  • 嵌入跨域iframe

  • 包含跨域资源,例如图像或脚本

  • 用 DOM 引用打开跨域弹出窗口

虽然上述这些情况存在跨域现象,但是浏览器进行一系列限制来防止不同源之间的不安全操作。例如来自一个源的JS只能读写自己源的DOM树不能读取其他源的DOM树,如果两个网页不同源,就无法拿到对方的DOM;例如通过对Cookie设置SameSite属性来防止跨站攻击等。所有这些策略决策都为同一个浏览上下文组中的内容提供了安全环境。

一般来说这已经足够安全了,直到Spectre漏洞(幽灵漏洞)的出现。

Spectre漏洞

Spectre漏洞从原理上来说就是缓冲时延旁路攻击的一种实际攻击方法,Spectre攻击可以成功主要由于以下几个条件:

1.缓冲时延旁路。 所谓旁路就是在你的程序正常执行之外,产生了一种边缘特征,这些特征反映了你不想产生的信息,这个信息被人拿到可以进行分析,你就泄密了。在Spectre攻击中利用的旁路是缓冲延时旁路。一般访问一个变量,这个变量在内存中,这需要上百个时钟周期才能完成,但如果你访问过一次,这个变量被加载到缓冲(Cache)中了,下次你再访问,可能几个时钟周期就可以完成了。所以就可以利用这个特征,诱导被攻击对象用攻击者感兴趣的地址的内容作为下标访问一个数组,然后攻击者检查这个数组中每个成员的访问时间就可以得到感兴趣的地址的内容。

2.CPU的预执行机制。 我们认为有一个恶意程序去查询一个没有权限的信息,操作系统会返回禁止的信息,这个逻辑没有问题。但是其实当恶意程序询问时,操作系统在做判断的同时,cpu会因为预测机制会去执行。比如:1.if(condition)do_sth(); 2.  我们以为condition不成立,do_sth就不会执行,但condition存在内存上,从内存中把condition读出来,可能要几百个时钟周期,CPU闲着也是闲着,于是,它偷偷把do_sth()给它执行了。CPU本来想得好好的:我先偷偷执行着,如果最终condition不成立,我把动过的寄存器统统放弃掉就可以了。问题是,大部分CPU在执行do_sth()的时候,如果有数据被加载到Cache中了,它是不会把它清掉的(因为这个同样不影响功能),于是就制造了一个“旁路”。我们来看spectre攻击的核心代码。操作系统会试图确保一个程序无法访问属于其他程序的内存区块,不同程序使用的内存快被隔开,所以程序无法读取被攻击者的数据。但利用上面两个条件,可以“偷取”到本无权限访问的内存的区块内容。

img

// if (x < A.length) {  // 分支预测,x可能会远大于A的长度 index = A[x];  
// 利用分支预测访问无权限地址内容  localJunk = Instrument[index]; // 每个字符的范围是[0,255],Instrument长度为256, }// 遍历Instrument,通过访问时间推测出index是4 

```综上,幽灵漏洞需要具备两个条件:`

1. 与攻击对象共享一段内存(存放Instrument);

2. 让攻击对象执行一段访问secret的代码。同时攻击者需要测量从内存中读取特定值所需的时间,为此需要一个可靠且精确的计时器。

performance.now()SharedArrayBuffer`正好可以用作计时器

SharedArrayBuffer如何作为高精度计时器可以参考www.yinchengli.com/2022/08/20/…

我们都知道 worker 在浏览器中有很大的限制,比如不能访问 window, document 对象, 但SharedArrayBuffer 提供了一段共享内存这就会导致 worker 是有办法通过 SharedArrayBuffer 攻击获取到主线程的敏感信息。利用 Spectre ,使得加载到与代码同一的浏览上下文组中的任何数据都具有可读性,攻击者可以读取到在同一浏览器下 Context Group 下的任何资源。 同时利用SharedArrayBuffer还可以获取高精度时间,这更为网页攻击下获取数组成员的访问时间提供了便利。

所以在2018年 Spectre 漏洞暴露后,所有主流浏览器都默认关闭了SharedArrayBuffer以应对,且降低了performance.now()的时间精度。后续浏览器陆续重新启用了SharedArrayBuffer。Chrome 在92 之前,是默认开启 SharedArrayBuffer 的,但是已经弹出警告信息提示 SharedArrayBuffer 将会在 92 版本必须启用跨域隔离。Chrome92后就必须在跨域隔离的状态下才能使用SharedArrayBuffer。

除了SharedArrayBuffer还有其他一些API必须在跨源隔离的环境下使用,例如:

  • performance.measureUserAgentSpecificMemory() :测量网页的内存使用情况。

  • performance.now()、performance.timeOrigin 解析时间限制为 100 微秒左右。通过跨源隔离,解析时间可以达到 5 微秒左右。

跨域隔离策略

在浏览器中,不同网站的不同文档可以在同一进程中运行,两个网站共享一个浏览上下文组,可能处于同一进程中,攻击者可利用Spectre等漏洞窃取用户信息。为了减轻这种风险,浏览器提供了一个基于选择加入的隔离环境,称为“跨源隔离”,上述那些API都需要在跨域隔离的环境下才能使用。

跨域隔离通过HTTP首部,保证处于同一浏览上下文组的所有文档都来自可信任的源,且不同的浏览上下文应处于不同进程中,但不能保证所有浏览器都这样实现

跨源读取阻止(CORB)

跨源读取阻止-Cross Origin Read Blocking(CORB)并不是HTTP首部,而是站点隔离机制的一部分。该机制从Chrome 67开始默认启用。虽然站点隔离机制可以使得不同域的站点运行在不一样的进程中(而且考虑到内存并不是所有浏览器会这样实现),但是恶意网站仍然可以合法地请求跨域资源,例如利用标签来请求含有敏感信息的文件。

 

这个JSON文件会出现在该恶意站点的渲染器进程的内存中,渲染器发现这不是一个有效的图片格式,于是不渲染这张图片。但是在Spectre漏洞的帮助下,攻击者可以设法访问这部分内存以获取敏感信息。

CORB就是用于阻止这样的访问。如果一个响应被CORB阻止,这个响应甚至不会到达恶意站点所在的进程中。

被保护的数据类型只有 html xmljson。很明显

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部