重温Cloudflare Workers上的远程Spectre攻击

Hacker News Top 论文

摘要

本文重温了Cloudflare Workers上的远程Spectre攻击,展示了在现有防御措施下的一次成功生产环境攻击。它详细介绍了安全机制的改进,并宣布发布一篇关于这些发现的研究论文。

论文:<a href="https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2608.17043v1" rel="nofollow">https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2608.17043v1</a> ("远程计时器即服务:使用远程计时器的云端高效微架构泄漏")
查看原文
查看缓存全文

缓存时间: 2026/08/19 19:10

# 重新审视针对 Cloudflare Workers 的远程 Spectre 攻击 来源:https://blog.cloudflare.com/revisiting-spectre-attacks-on-workers/ 2021年,我们评估了针对 Cloudflare Workers 的远程 Spectre 攻击 (https://blog.cloudflare.com/spectre-research-with-tu-graz/)。基于评估结果,我们上线了一个名为动态进程隔离(Dynamic Process Isolation (https://blog.cloudflare.com/spectre-research-with-tu-graz/),简称 DyPrIs)的生产环境防御机制,该机制能够识别具有恶意行为特征的脚本,并将其隔离到单独的进程中。自那时起,稳定 Spectre 攻击的领域发现了更新的技术。为了理解这些技术是否对我们的 Workers 生产环境构成威胁,我们决定在内部重新评估远程 Spectre 攻击。在生产环境上构建更新的概念验证,使我们能够实证评估 Spectre 攻击在生产工作负载下的风险。 要在生产环境中成功实施侧信道攻击,外部攻击者必须克服额外的障碍,例如共享硬件资源上的活动、中断、上下文切换以及粗粒度计时器。我们的研究发现了 DyPrIs 实现中的一个局限性,并成功演示了在 Cloudflare Workers 生产环境中,一种能够以 99% 的准确率可靠泄露最高 12 bit/s 数据的远程 Spectre 攻击。作为这项研究的结果,我们改进了 DyPrIs,整合了 V8 沙箱和进程内隔离机制 (https://blog.cloudflare.com/safe-in-the-sandbox-security-hardening-for-cloudflare-workers/),以进一步降低内存泄露攻击的风险。 今天我们发布了一篇论文 (https://arxiv.org/pdf/2608.17043),描述了我们的研究发现,论文由 Albert Pedersen、Haocheng Xiao、Sam Ainsworth、Nigel Topham 和 Martin Schwarzl 共同撰写。本文涵盖了 2024 年及 2025 年初进行的研究。 需要注意的是,由于 Cloudflare Workers 运行时团队已采取缓解措施,所提出的攻击在当前生产系统中已被缓解。在过去的三年里,我们未发现任何主动利用的迹象。 ## Cloudflare Workers 安全模型 Cloudflare Workers 在边缘运行不受信任的 JavaScript。利用语言级别的隔离(以 V8 隔离区的形式),数万个租户可以共享同一个操作系统进程。每个 Worker 拥有自己独立的 JavaScript 堆。与完全进程隔离相比,这种设计保持了低启动延迟,并让我们能够非常高效地运行多个租户。围绕运行时,我们有多层防御,如自动化的 V8 补丁流水线、由 Linux 命名空间和 seccomp 过滤器组成的双层沙箱、Cap'n Proto RPC,以及在单独的进程沙箱中调度某些脚本的可能性。然而,Worker 进程内的单个任意读漏洞可能导致跨租户信息泄露。一种非常难以缓解的漏洞利用了推测执行的特性,即进程内 **Spectre**。 ## Spectre BLOG-3371 2.png 你可以将推测执行比作徒步旅行。在某个时刻,你到达一个岔路口,必须预测该往哪里走。如果预测正确,你就节省了一些时间,并可以在山间小屋享受阳光和清爽的饮料。然而,如果你推测错了方向,就必须返回。小径看起来未受破坏,但你的脚印却留在了泥土中。 CPU 中的推测执行原理类似。分支预测器提前对分支结果进行有根据的猜测,CPU 则推测执行该分支。如果预测正确,推测执行节省了时间。然而,如果预测不正确,CPU 必须丢弃结果、回滚并执行另一个分支。由于这些推测执行的指令只是临时存在于 CPU 流水线中,永远不会被永久退休或提交,文献中称它们为瞬态指令,并将这一概念概括为瞬态执行。 然而,由于瞬态执行,微架构状态中仍然会留下一些痕迹,例如在 CPU 缓存中。因此,攻击者可以利用 Spectre 瞬态地访问越界内存,将单个信息位编码到缓存状态中,并利用重新访问数据的延迟来推断该位是被设置还是未被设置。 为了缓解进程内 Spectre 攻击 (https://blog.cloudflare.com/spectre-research-with-tu-graz/),Cloudflare Workers 冻结本地计时器,禁止多线程和共享内存,并主动检测、定期混洗内存,并将具有恶意特征的脚本隔离到单独的进程中。 ## 攻击原语 BLOG-3371 3.png 远程 Spectre 攻击的高级概览。攻击者需要一个远程计时器来测量探测瞬态泄露的位是‘0’还是‘1’所需的时间。Cloudflare Workers 平台刻意限制了计时器 (https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/)。在仅 CPU 执行期间,时间实际上是冻结的。`Date.now()` 和 `performance.now()` 并未提供持续推进的高精度时钟。没有共享内存,也没有多线程,因此通过 `SharedArrayBuffer` 实现的典型对抗线程计时器不可用。 要成功发动攻击,必须解决几个挑战。首先,Workers 运行时是有限的,并且必须保证攻击者和受害者之间的共置。其次,必须发现一个可靠的、最好是共置的远程计时器,以允许稳定的计时测量。第三,攻击在生产条件下运行,这意味着需要额外的稳定性措施,例如一个可靠的 Spectre 小工具来启用瞬态 64 位越界访问、强健的信号放大以应系统和网络噪声,以及一个能够可靠驱逐缓存数据的原语。 ### Spectre 小工具 `` return probeArray[ obj instanceof ObjP ? PROBEARRAY_OFFSET + ((obj.ptr[0] >> bit) & 1) * 0x800 : 0x400 ]; ``` *推测类型混淆 Spectre 小工具* 使用合适的 Spectre 小工具(如上代码片段),攻击者可以瞬态访问越界内存,并将单个位编码到缓存(`probeArray`)中。然后,攻击者测量内存访问延迟以确认数据是否被缓存。更快的访问意味着该行被缓存,且该位为 1。相反,较慢的访问意味着未被缓存,且该位为 0。在我们的攻击中,我们使用了两种不同类型的 Spectre 小工具。第一种泄露压缩堆指针,例如隔离区的堆基地址(root),第二种则利用推测类型混淆从任意的、攻击者构造的用户空间 64 位指针泄露数据。在进行研究时,V8 沙箱尚未在 Cloudflare Workers 上实现。在指针压缩下,大多数对象使用 32 位压缩指针。`TypedArray` 是少数仍然存储原始 64 位指针指向其后备存储的例外之一,而这正是我们的小工具所利用的。 分支 `obj instanceof ObjP` 执行类型检查,即一个分支。为了错误训练分支预测,我们在真实的 `ObjP` 实例上调用该小工具多次,然后在一个具有攻击者控制内存布局 `ObjI` 的不同对象上调用它。CPU 推测执行已采取的分支并沿着 `obj.ptr[0]` 执行,即使该对象具有不同的类型。为了泄露单个位,我们屏蔽出一个位,并使用它来选择两个 `probeArray` 行中的一个。该行是否被缓存编码了该位的值。 利用堆泄露小工具,我们映射相邻对象并定位一个攻击者控制的数组。我们的第二种小工具混淆了两个跨越多个缓存行的大对象,因此类型字段位于与我们读取字段不同的缓存行上。驱逐类型字段打开了推测窗口,而目标字段保持缓存,瞬态读取则遵循一个攻击者控制的 64 位值。这将泄露转变为任意地址读取。该技术的更详细描述可在论文中找到。 **泄露任意 64 位地址的本地演示。** BLOG-3371 4.png 推测类型混淆小工具的内存布局。精心构造的伪造类型化数组头部让瞬态读取跟随攻击者选择的指针。### 信号放大 缓存命中和缓存未命中之间相差几纳秒。此外,远程计时器在微秒到毫秒的尺度上存在噪声。因此,需要某种形式的信号放大来区分缓存命中和未命中。Stephen Röttger 和 Artur Janc 发现了一种方法来 放大单次内存访问 (https://security.googleblog.com/2021/03/a-spectre-proof-of-concept-for-spectre.html),通过利用 L1 缓存中基于树的伪最近最少使用(PLRU)缓存替换策略。基于树的 PLRU 将每个缓存集组织为一棵二叉树,节点指向最近最少使用的一侧,因此 CPU 通过遵循这些指针来驱逐缓存行。通过正确的访问模式,攻击者可以无限期地保持目标行缓存,只要在指针转向目标时触及其树邻居即可。相当优雅,对吧?利用这种行为,单个缓存事件的时序可以被任意放大,从而在一种情况下导致大量 L1 命中(更快),而在相反情况下导致大量 L1 未命中。 下图说明了内存地址 X 是否被缓存。如果未被缓存,访问模式导致大量缓存命中。如果存在,它占据树中的一个节点,随后四个缓存行试图挤入三个节点,导致大量 L1 未命中。 BLOG-3371 5.png 用于放大单个缓存事件(命中/未命中)的 PLRU 访问模式。### 远程计时器 只要信号能被放大,有噪声的远程计时器就足以区分编码的位。例如,一个连接到提供高分辨率时间戳的外部服务器的 WebSocket 连接就足够了。计时器可以托管在 Cloudflare 或与运行 Worker 的目标数据中心共置的数据中心中。Worker 向远程计时器请求为特定事件标记时间戳,并在事件停止后为另一个请求计算时间差。 在论文中,我们评估了几种不同的计时器设置,即使跨越较大的拓扑距离,也能仅用少量样本可靠地实现亚毫秒级分辨率。下图显示了使用基于树的 PLRU 放大技术放大的缓存事件。 BLOG-3371 6.png 50 次放大后的缓存命中(实线)和缓存未命中(虚线)计时测量的核密度估计。上图:网络计时器因抖动显示重叠。下图:真实值显示清晰分离。垂直线分别标记中位数。### 可重复的测量 单次测量不足以可靠地区分时序编码的数据。生产机器存在噪声,因此攻击者必须将每次测量重复至少几次,并使用某种统计判别器。在我们的情况下,重复测量意味着重置缓存状态。每轮之前,有两件事必须被驱逐出缓存。推测分支依赖的值必须被驱逐,以便分支解析停滞足够长的时间以打开推测窗口。编码泄露位的探测行必须被驱逐,以便下一次瞬态访问可以重新缓存它。 由于 JavaScript 中没有直接可用的指令,经典的方法是构建一个驱逐集。驱逐集是一组与目标映射到相同缓存集的地址。以正确的模式访问它们会将目标推出缓存。在他们的攻击中,Stephen Röttger 和 Artur Janc 使用驱逐列表来可靠地至少驱逐到 L2 缓存。这可行,但代价高昂。构建精确的驱逐集需要许多计时测量,而我们的计时器是有噪声的远程计时器。之前针对 Workers 的远程攻击通过每轮遍历一个大于 L1 和 L2 缓存的数组来回避搜索。这是一个选项,但更慢。 Dougall Johnson 在他关于可移植 JavaScript Spectre 利用 (https://dougallj.wordpress.com/2021/03/16/another-approach-to-portable-javascript-spectre-exploitation/) 的精彩博客文章中描述了一种更优雅的方式。该想法直接源于鸽巢原理。如果你分配的数据远多于缓存能容纳的数据,那么随机选择的缓存行几乎肯定未被缓存。对于 256 KB 的 L2 缓存,分配 64 MB 留下最多 **1/256** 的概率,随机缓存行仍在 L2 中。因此,与其驱逐特定行,你根本不驱逐。你选择一个新位置,该位置以压倒性概率已被驱逐。频繁循环遍历该对象数组的一个很酷的副作用是,这会导致自动驱逐效应。 为了在 JavaScript 中利用这一点,我们分配了大量攻击者和受害者对象对,其大小超过末级缓存。每次测量轮次选择一个新的随机对。对象的映射指针(推测类型检查读取的隐藏类描述符)因此几乎肯定已被驱逐。 ### 共置攻击者和受害者隔离区 为了使攻击成功,攻击者和受害者隔离区必须被调度到同一边缘服务器的同一进程中。人们可能直观地认为这很困难,考虑到 Cloudflare 运营着数万个边缘服务器,但这在 Cloudflare Workers 上实际上相当容易。因为 Cloudflare Workers 设计为可在任何 Cloudflare 边缘服务器上执行,所以从攻击者脚本中通过 `fetch("https://victim.example")` 调用受害者脚本,在大多数情况下会导致调度器在完全相同的进程中启动受害者 Worker 的实例。受害者隔离区可以通过定期向其发送子请求来保持活跃。 更重要的是,由于攻击稳定性高度依赖于运行 Worker 脚本的边缘服务器的 CPU 负载,这允许攻击者在非高峰时段的 colo(例如在欧洲工作时间的澳大利亚 colo)战略性地运行攻击,那里的流量水平相对较低。 ### 击败隔离区资源限制 Cloudflare Workers 运行时对所有隔离区执行一组限制 (https://developers.cloudflare.com/workers/platform/limits/),以保护平台并防止滥用。对于本次攻击而言,相关的限制是每次调用 30 秒的 CPU 时间和 1,000 次子请求。这些限制后来已被提高 (https://developers.cloudflare.com/workers/platform/limits/#account-plan-limits),但以下原则仍然适用。 对于常规 Worker,每个 HTTP 请求(一个 fetch 事件)都是一次新的调用,会重置这些限制。问题在于将顺序请求落在同一边缘服务器上。负载均衡和不断变化的网络条件使其不可靠。持久对象(Durable Objects)为我们解决了这个问题。 持久对象 (https://developers.cloudflare.com/durable-objects/) 专为客户端间的实时协调而构建,因此运行时将每个传入的 WebSocket 消息视为一次重置 CPU 时间和请求限制的调用。攻击者打开一个到持久对象 Worker 的持久 WebSocket,并定期发送保活消息。这使单个隔离区保持活跃,并为我们提供了一个持久的双向通道来运行攻击。 一个特殊之处花费了我们一些时间。隔离区是单线程的,因此传入的 WebSocket 消息只在脚本将控制权交还给事件循环时才会被处理。在同步

相似文章

网络钓鱼者正在劫持合法的云基础设施

Hacker News Top

卡巴斯基分析了网络钓鱼者如何越来越多地滥用合法的云平台(如 Cloudflare Workers、Vercel 和 GitHub Pages)来托管钓鱼页面,并通过中间人攻击绕过多因素认证。

突破防御:利用段错误绕过 Intel CET

Lobsters Hottest

该仓库提供了面向段错误编程(SFOP)的工件,这是一种利用信号处理器绕过 Intel CET 的新型利用技术。它包含针对 Nginx 和 Ladybird 的 PoC 利用和演示。