桌面版 Chrome 67 有一项默认启用网站隔离的新功能。本文介绍了网站隔离的含义、必要性,以及网站开发者为何应予以关注。
什么是网站隔离?
互联网上可用于观看猫咪视频、管理加密货币钱包等,但您不希望fluffycats.example
能够访问您珍贵的加密货币!幸运的是,由于同源政策,网站通常无法在浏览器中访问彼此的数据。不过,恶意网站可能会尝试绕过此政策攻击其他网站,有时,系统会在执行同源政策的浏览器代码中发现安全 bug。Chrome 团队致力于尽快修复此类错误。
网站隔离是 Chrome 中的一项安全功能,可提供一道额外的防线来降低此类攻击的成功率。它可确保不同网站的网页始终被放入不同的进程中,每个进程均在限制允许进程执行的操作的沙盒中运行。它还会阻止进程从其他网站接收特定类型的敏感数据。因此,借助网站隔离功能,恶意网站更加难以利用 Spectre 等推测性边信道攻击从其他网站窃取数据。随着 Chrome 团队完成额外的违规处置措施,即使攻击者的页面能够破坏自身进程中的某些规则,网站隔离功能也会有所帮助。
网站隔离功能可有效地提高不可信网站在其他网站访问或窃取您帐号的信息难度。它能够针对各种类型的安全漏洞(例如最近的 Meltdown 和 Spectre 边信道攻击)提供额外的保护。
如需详细了解网站隔离功能,请参阅 Google 安全博客上的文章。
跨域读取屏蔽
即使所有跨网站网页都被放入单独的进程中,网页仍然可以合法地请求一些跨网站子资源,例如图片和 JavaScript。恶意网页可能会利用 <img>
元素加载包含敏感数据(例如您的银行余额)的 JSON 文件:
<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->
如果没有网站隔离功能,JSON 文件的内容会写入渲染器进程的内存,此时渲染程序会注意到它不是有效的图片格式,并且不会渲染图片。但是,攻击者随后可能会利用 Spectre 等漏洞来读取该内存块。
除了使用 <img>
,攻击者还可以使用 <script>
将敏感数据提交到内存:
<script src="https://your-bank.example/balance.json"></script>
跨域读取阻塞 (CORB) 是一项新的安全功能,可根据其 MIME 类型防止 balance.json
的内容进入渲染器进程内存的内存。
我们来详细了解 CORB 的运作方式。网站可以从服务器请求两种类型的资源:
- 数据资源,例如 HTML、XML 或 JSON 文档
- 媒体资源,例如图片、JavaScript、CSS 或字体
网站可以从自己的来源或从具有宽松的 CORS 标头(例如 Access-Control-Allow-Origin: *
)的其他来源接收数据资源。另一方面,即使没有宽松的 CORS 标头,也可以包含来自任何来源的媒体资源。
在以下情况下,CORB 会阻止渲染器进程接收跨源数据资源(即 HTML、XML 或 JSON):
- 该资源具有
X-Content-Type-Options: nosniff
标头 - CORS 未明确允许访问资源
如果跨源数据资源未设置 X-Content-Type-Options: nosniff
标头,CORB 会尝试嗅探响应正文,以确定是 HTML、XML 还是 JSON。这是必要的,例如,某些 Web 服务器配置有误,并以 text/html
的形式提供图片。
虽然请求仍在后台发生,但被 CORB 政策屏蔽的数据资源将作为空数据呈现给进程。因此,恶意网页很难将跨网站数据拉入其进程中窃取数据。
为了获得最佳安全性并受益于 CORB,我们建议您执行以下操作:
- 请使用正确的
Content-Type
标头标记响应。(例如,HTML 资源应以text/html
的形式提供、采用 JSON MIME 类型的 JSON 资源和采用 XML MIME 类型的 XML 资源)。 - 使用
X-Content-Type-Options: nosniff
标头可以停用嗅探功能。如果没有此标头,Chrome 会快速进行内容分析,以尝试确认类型是否正确,但由于这样做有助于避免响应,避免阻塞 JavaScript 文件等内容,因此最好自己做正确的事。
如需了解详情,请参阅面向 Web 开发者的 CORB 文章或我们的 CORB 深度解释文档。
Web 开发者为何应关注网站隔离功能?
在大多数情况下,网站隔离是浏览器的一项后台功能,未直接提供给 Web 开发者。例如,没有可供学习的新 Web API。一般来说,无论是否启用网站隔离功能,网页都不应区分运行时两者的区别。
不过,此规则也有一些例外情况。启用网站隔离功能会带来一些细微的副作用,可能会影响您的网站。我们维护着一个已知的网站隔离问题列表,并在下面详细说明了最重要的问题。
整页版式不再同步
使用网站隔离功能时,无法保证整个页面布局一定是同步的,因为一个页面的帧现在可能分散在多个进程中。如果网页认为布局更改会立即传播到网页上的所有帧,就可能会影响网页。
例如,假设有一个名为 fluffykittens.example
的网站与 social-widget.example
上托管的社交微件进行通信:
<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
const iframe = document.querySelector('iframe');
iframe.width = 456;
iframe.contentWindow.postMessage(
// The message to send:
'Meow!',
// The target origin:
'https://social-widget.example'
);
</script>
最初,社交 widget 的 <iframe>
的宽度为 123
像素。然后,FluffyKitten 页面会将宽度更改为 456
像素(触发布局),并向社交微件发送一条消息,其中包含以下代码:
<!-- https://social-widget.example/ -->
<script>
self.onmessage = () => {
console.log(document.documentElement.clientWidth);
};
</script>
每当社交微件通过 postMessage
API 收到消息时,都会记录其根 <html>
元素的宽度。
系统会记录哪种宽度值?在 Chrome 启用网站隔离功能之前,答案是 456
。访问 document.documentElement.clientWidth
会强制执行布局,而在 Chrome 启用网站隔离功能之前,布局之前是同步的。不过,启用网站隔离功能后,跨源社交微件重新布局现在会在单独的进程中异步进行。因此,现在答案也可以是 123
,即旧的 width
值。
如果网页更改了跨源 <iframe>
的大小,然后向其发送 postMessage
,而使用网站隔离功能,则接收帧在接收消息时可能还不知道其新大小。从更笼统的角度来说,如果页面假定布局更改会立即传播到页面上的所有帧,则可能会破坏页面。
在此特定示例中,一个更可靠的解决方案是在父框架中设置 width
,并通过监听 resize
事件来检测 <iframe>
中的变化。
卸载处理程序可能会更频繁地超时
当某个帧导航或关闭时,旧文档以及其中嵌入的任何子框架文档都会运行其 unload
处理程序。如果新的导航发生在同一渲染程序进程中(例如,对于同源导航),则旧文档及其子帧的 unload
处理程序可以在允许提交新导航之前运行任意时长。
addEventListener('unload', () => {
doSomethingThatMightTakeALongTime();
});
在这种情况下,所有帧中的 unload
处理程序都非常可靠。
但是,即使没有网站隔离功能,某些主框架导航也是跨进程的,这会影响卸载处理程序的行为。例如,如果您通过在地址栏中输入网址从 old.example
导航到 new.example
,new.example
导航将在新进程中发生。显示 new.example
页面后,old.example
及其子帧的卸载处理程序会在后台在 old.example
进程中运行,并且如果旧卸载处理程序未在特定的超时时间内完成,则其终止。由于卸载处理程序可能不会在超时之前完成,因此卸载行为不太可靠。
使用网站隔离功能,所有跨网站导航都将是跨进程的,这样来自不同网站的文档就不会彼此共享进程。因此,上述情况在更多情况下适用,并且 <iframe>
中的卸载处理程序通常具有上述后台和超时行为。
网站隔离功能的另一个不同之处是卸载处理程序的新并行顺序:如果不使用网站隔离功能,卸载处理程序会严格按照自上而下的顺序运行各个帧。但使用网站隔离功能时,卸载处理程序会在不同的进程中并行运行。
这些是启用网站隔离功能的基本后果。Chrome 团队致力于在可行的情况下提高卸载处理程序的可靠性。我们还发现一些 bug,即子帧卸载处理程序尚无法使用某些功能,并且正在努力解决这些问题。
卸载处理程序的一个重要情况是发送会话结束 ping。此操作通常按照以下步骤操作:
addEventListener('pagehide', () => {
const image = new Image();
img.src = '/end-of-session';
});
鉴于此变更,更好的方法是改用 navigator.sendBeacon
:
addEventListener('pagehide', () => {
navigator.sendBeacon('/end-of-session');
});
如果您需要更好地控制请求,可以使用 Fetch API 的 keepalive
选项:
addEventListener('pagehide', () => {
fetch('/end-of-session', {keepalive: true});
});
总结
网站隔离功能可将每个网站隔离到各自的进程中,从而使得不受信任的网站更难以访问或窃取您在其他网站上的帐号信息。在此期间,CORB 会尝试将敏感数据资源排除在渲染程序进程之外。上述建议可确保您充分利用这些新的安全功能。
感谢 Alex Moshchuk、Charlie Reis、Jason Miller、Nasko Oskov、Philip Walton、Shubhie Panicker 和 Thomas Steiner 阅读本文的草稿版本并提供反馈。