当前位置:   article > 正文

由一个报错引发的浏览器跨域隔离探索_cross-origin-resource-policy

cross-origin-resource-policy

背景

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

不久前有一些用户反馈,他的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);
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
// worker.js
onmessage = function (ev) {console.log(ev.data);postMessage('hello');
} 
  • 1
  • 2
  • 3

主线程新建了一个 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; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
//
// Worker 线程
onmessage = function (ev) {// 主线程共享的数据,就是 1KB 的共享内存const sharedBuffer = ev.data;// 在共享内存上建立视图,方便读写const sharedArray = new Int32Array(sharedBuffer);console.log(sharedArray[0]);// 123
}; 
  • 1
  • 2
  • 3
  • 4

复制机制接收 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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开始默认启用。虽然站点隔离机制可以使得不同域的站点运行在不一样的进程中(而且考虑到内存并不是所有浏览器会这样实现),但是恶意网站仍然可以合法地请求跨域资源,例如利用<img>标签来请求含有敏感信息的文件。

<img src="https://your-site/secret.json"> 
  • 1

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

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

被保护的数据类型只有 html xmljson。很明显 <script><img> 等跨域标签应有的 MIME type 和 htmlxmljson 不一样。

为了最佳安全策略,建议开发者:

1.为响应内容标记正确的 Content-Type

2.使用 X-Content-Type-Options: nosniff 禁止 MIME sniffing,如此,可以让浏览器不进行内容 MIME 类型嗅探,从而更简单快速地保护资源或响应返回。

内容嗅探技术是指 当响应头没有指明 MIME type 或 浏览器认为指定类型有误时,浏览器会对内容资源进行检查并执行,来猜测内容的正确 MIME 类型。

跨源资源策略(CORP)

跨源资源策略-Cross Origin Resource Policy(CORP)是响应首部,有三个取值:same-originsame-sitecross-origin

same-origin: 只允许同源加载,跨域隔离下的默认值。

same-site: 允许同站点加载。

cross-origin: 允许跨源加载,一般情况下的默认值。

未声明该首部时,浏览器将其当作Cross-Origin-Resource-Policy: cross-origin

同站点same-site

same-site的意思则是只有请求来自同一个eTLD+1,才允许加载响应。什么是eTLD,在这之前先理解一下什么是站点。.com.org等域被称之为顶级域(TLD,top level domain),一个站点就是顶级域加上前面的二级域,例如www.example.com的站点就是example.com。但有一些域,如.``edu.cn.github.io等,只使用顶级域加二级域不足以构成有效站点,而且也没什么特别的规则来确定这样的域。于是定义了“实际顶级域”(eTLD,effective top level domain),维护一个列表来确定哪些是实际顶级域。那么一个站点就定义为eTLD加上前一个域,简称eTLD+1。比如www.example.com.cn的站点就是example.com.cn

跨源嵌入程序策略(COEP)

跨源嵌入程序策略-Cross Origin Embedder Policy(COEP)响应首部,有两个取值:require-corpunsafe-none

require-corp: 可以让站点仅加载同域资源或是明确标记为可共享的跨域资源,即跨域资源需要显式声明Cross-Origin-Resource-Policy: cross-origin 才允许被加载。若未声明Cross-Origin-Resource-Policy首部,则将其当作same-origin

unsafe-none: 默认值。

需要注意,Cross-Origin-Embedder-Policy: require-corp会递归地对所有子资源和框架生效。也就是说,如果文档嵌入了一个启用了COEP的文档,那么这个子文档中的所有跨源资源也同样需要启用CORP(或CORS)。为了实现跨源隔离,这样的递归生效是必要的。

该首部旨在提供这样的环境,即当设定Cross-Origin-Embedder-Policy: require-corp时,Cross-Origin-Resource-Policy首部的默认值为same-origin。服务器需要显式为跨源使用的资源声明Cross-Origin-Resource-Policy: cross-origin。这样的策略也表明安全责任在服务器一方,即服务器需要分辨哪些请求是安全的,再决定是否为响应加上CORP首部。

COEP扩展了CORP的能力,让CORP能够处理由<iframe><img>等产生的跨源导航请求。也就是说,在启用COEP的情况下,若文档未声明Cross-Origin-Resource-Policy: cross-origin,不可以使用或嵌入该文档。

前面提到,站点隔离机制会将跨源的、、等嵌入元素储存在独立进程中,那为什么还需要使用COEP禁用跨源嵌入框架?因为实现OOPIFs(即Out-of-Process iframes)会显著增加浏览器的内存使用,并非所有浏览器都有实现计划(如为低内存手机设计的浏览器等) 。而没有相关实现的浏览器可能会在同一进程中加载框架。

跨源开放者策略(COOP)

跨源开放者政策-Cross Origin Opener Policy(COOP)是响应首部,有三个取值:unsafe-nonesame-originsame-origin-allow-popups

只设置COEP并不安全,至少有两个隐患:

1.恶意网站可使用Window.open()在新窗口中打开普通网站,再通过新窗口的window对象将其导航至恶意目标网站中(反过来,新打开的窗口也可以访问原窗口的window对象,对此Chrome和Firefox已有措施)。

2.由于两个网站共享一个浏览上下文组,可能处于同一进程中,攻击者可利用Spectre等漏洞窃取用户信息

unsafe-none:默认值,表示新打开的文档与原文档处于同一浏览上下文。

same-origin:打开一个声明了same-origin的文档会新建浏览上下文,除非原文档也声明了相同的COOP值,且与新文档同源。不同源的文档间不能相互访问window对象。例如,文档A使用window.open()打开了声明same-origin的跨源文档B,则window.open()的返回值应为null(规范如此规定,但目前暂无相应的浏览器实现,Chrome和Firefox均返回一个相当于空白页的window对象),B的window.opener也会是null。

same-origin-allow-popups:与same-origin类似,但若声明该值的文档在辅助浏览上下文中打开(如window.open()),则允许与原文档共享浏览上下文。

由上面可知,文档处于同一浏览上下文的条件是所有文档都声明了相同的COOP值。

跨源资源共享(CORS)

跨源资源共享-Cross Origin Resource Sharing(CORS)在我们日常解决跨域问题时经常会使用,这个我们已经非常熟悉了,它是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。

详细介绍:developer.mozilla.org/zh-CN/docs/…

开启跨域隔离

上述介绍了跨域隔离相关的策略,接下来介绍一下如何使用这些策略,才能让我们在页面中使用SharedArrayBuffer等特定功能。

主文档侧

要开启跨域隔离状态,首先需要在主文档加上以下HTTP响应标头:

Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin 
  • 1
  • 2
  • 第一个标头指示浏览器阻止加载那些未选择加入跨域文档的资源或iframe。即阻止文档加载任何未明确授予文档许可权的跨域资源(使用CORP或CORS)。

  • 第二个标头阻止了跨域窗口与主文档进行交互,同时也阻止跨域窗口与主文档共享一个浏览器上下文组。即除非位于具有相同 COOP 设置的相同源中,否则同源窗口和从该文档打开的窗口将具有单独的浏览上下文组。因此,对打开的窗口强制执行隔离,同时禁止两个窗口相互通信。

在Chrome等主流浏览器中,通过检查 self.crossOriginIsolated 可以确定网页是否处于跨源隔离状态。

即使声明了正确的COEP和COOP,部分用户代理仍有可能将crossOriginIsolated设为false。是否能使用SharedArrayBuffer等API将取决于crossOriginIsolated,而不止COEP和COOP。

if(self.crossOriginIsolated){
 // 跨域隔离成功,重新启用SharedArrayBuffer等API
} 
  • 1
  • 2
  • 3

子文档侧和资源

启用CORP

//
// 根据具体情况选择same-site、cross-origin或same-origin
Cross-Origin-Resource-Policy: cross-origin 
// 如果是iframe还需要设置COEP
Cross-Origin-Embedder-Policy: require-corp 
  • 1
  • 2
  • 3
  • 4
  • 5

如果是同源,可以不显式声明Cross-Origin-Resource-Policy:same-origin,跨源可根据实际情况选择same-site或cross-origin。

启用CORS

针对<audio><img><link><script><video>标签,可以设置crossorigin属性,如果资源支持CORS,也可以访问。

<img crossorigin="anonymous" src="xxx"/>
//
// 资源的响应头必须设置Access-Control-Allow-Origin
Access-Control-Allow-Origin:xxx 
  • 1
  • 2
  • 3
  • 4

所做的这一切只能说提升攻击者进行Spectre攻击的成本,这里之所以是说提升成本还非彻底解决是因为这个漏洞是基于硬件层面的,所以软件层面只能做有限的修复。虽然,CPU的分支预测是无法泄漏跨进程的内存内容的。因此,幽灵漏洞才强调在跨进程场景,两个进程需要共享一个array的内存,然后诱导被攻击的进程使用array去访问scecret内存,使得一些旁路信息泄露在array中。然后,发起攻击的进程在array中分析这些旁路信息,推测secret内存中的内容。但是,还是存在一些手段进行跨进程攻击,例如使用mmap来实现跨进程共享内存。

示例及调试

接下来我们看一些例子,以及在Chrome DevTools中如何调试。在DevTools的应用面板内,可以查看顶级框架的安全性和隔离状态。如下图,是未开启跨域隔离的状态。

设置主文档响应头

让我们来开启主文档的跨域隔离。

'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin', 
  • 1
  • 2

可以看到,所有未明确授予文档许可权的跨域资源都无法加载,而带有CORP的cdn资源可以正常加载。

查看network,发现这些不能加载的资源资源都被block了。

同时,弹出的跨域窗口也无法访问到主文档,即在弹出的跨域窗口中获取window.opener只能得到null

img

设置资源响应头

对block的资源增加CORP响应头,由于例子里面只是端口不一致,属于同一个站点,所以设置same-site即可。

'Cross-Origin-Resource-Policy': 'same-site' 
  • 1

由于未设置COEP,iframe资源仍然不能加载,且block的提示也变为COEP。

加上COEP的设置后就正常加载了。

'Cross-Origin-Embedder-Policy': 'require-corp' 
  • 1

跨域隔离报告

启用跨域隔离将阻止未明确选择加入的跨域资源进行加载,并且会阻止顶级文档与弹出窗口进行通信。未对所有资源设置正确标头,会影响网站的运行。如果能在不进行任何破坏的情况下就可以评估启用跨域隔离对您的网站的影响,就可以提前做好准备。Cross-Origin-Opener-Policy-Report-OnlyCross-Origin-Embedder-Policy-Report-Only HTTP 标头就是为此实现的。

1.在顶级文档上设置Cross-Origin-Opener-Policy-Report-Only: same-origin。正如标头名称所示,该标头只发送有关COOP: same-origin将会对您网站产生的影响的报告,而不会实际弹出窗口来禁止通信。

2.在顶级文档上设置Cross-Origin-Embedder-Policy-Report-Only: require-corp。与之前一样,该标头能够看到启用COEP: require-corp后的影响,而不会实际影响网站功能。

3.设置报告内容并配置一个网络服务器来接收和保存报告。

总结

1.SharedArrayBuffer是用来和线程之间进行数据交换访问的高效方法,被大量应用,例如WebAssembly 就使用 Worker 模拟了多线程。SharedArrayBuffer等可以用来当作高精度的计时器,为Spectre漏洞带来了方便。

2.为了降低Spectre漏洞带来的危害,SharedArrayBuffer等支持高精度时间计算的API及可能方便为spectre漏洞提供内存共享的API都需要在跨域隔离的状态下才能启用。

3.主文档启用跨域隔离后,可以通过检查crossOriginIsolated来确认跨域隔离是否开启成功,成功后将重新开启SharedArrayBuffer等API。当主文档开启跨域隔离,内部加载的资源都需要设置正确的标头,否则无法加载。同时,通过主文档弹出的跨域窗口将不再能与主文档通信。

 // 主文档 Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin 
 // 资源——CORP 
 // 根据具体情况选择same-site、cross-origin或same-origin Cross-Origin-Resource-Policy: cross-origin  
 // 如果是iframe还需要设置COEP Cross-Origin-Embedder-Policy: require-corp 
 // 资源——CORS,针对<audio>、<img>、<link>、<script> 和 <video>标签 Access-Control-Allow-Origin:xx 
 // 引用时标签内设置crossorigin <img crossorigin="anonymous" src="xxx"/> ```
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/773670
推荐阅读
相关标签
  

闽ICP备14008679号