翻译: 看雪翻译小组-Nxe
校对: 看雪翻译小组-fyb波
原文链接:https://googleprojectzero.blogspot.com/2020/02/escaping-chrome-sandbox-with-ridl.html
太长不看: 可以利用泄露跨进程内存的漏洞来进行Chrome沙箱的逃逸。在发起攻击之前, 攻击者仍需破坏渲染器. 为了防止在受影响的CPU平台上受到此类攻击, 请确保你的微代码(microcode)是最新的并且关闭了超线程(hyper-threading, HT).
在我上一篇客座博客"破坏数据流"("Trashing the Flow of Data")中, 我描述了如何利用Chrome的JavaScript V8引擎中的漏洞来获取渲染器中的代码执行权限。为了让该攻击变得有用, 你通常需要第二个漏洞以实现利用链, 因为Chrome沙箱限制了你对OS的访问, 而且网站隔离(Site Isolation)功能将跨站渲染器移到了各自的进程中, 以防止绕过网页平台的限制.
在这篇文章中, 我们将研究特别是在RIDL以及相似的硬件漏洞影响下, 受攻击的渲染器使用的沙箱。Chrome的IPC机制Mojo基于用于消息路由的机密, 泄露这些机密会允许我们向特权接口发送信息, 并执行渲染器不被允许执行的操作。我们利用这点来读取任意本地文件, 以及在Windows系统上运行沙箱外的.bat文件。 在这篇文章写作时, Apple和Microsoft都在与Chrome安全团队合作, 积极地尝试修复以防止此类攻击.
下图是简化版的Chrome进程模型:
渲染器进程分别运行在各自的沙箱中, 并且它们对内核的访问都是受限制的, 比如通过Linux上的seccomp过滤器或者Windows上的win32k lockdown。但是为了使渲染器执行任何有用的操作, 它需要与其他进程通信以执行各种操作。例如要加载图像时, 它会请求网络服务代表其获取图像.
Chrome中用于进程间通讯的默认机制叫作Mojo。它支持信息/数据管道和共享内存, 但是你通常会使用其中C++, Java和JavaScript实现的高级语言绑定。 也就是说, 你以一个自定义的接口定义语言(IDL)创建带有方法的接口, Mojo会生成你选择的语言的桩, 你只需实现其中的功能。 要想看看实际的情况, 你可以浏览.mojom IDL中的URLLoaderFactory, C++ 实现和渲染器中的使用。
一个值得注意的功能是, Mojo允许你通过现有通道转发IPC端点。该功能在Chrome里被广泛使用, 也就是你在.mojom文件里看到的pending_receiver或pending_remote参数。
其内部机制是, Mojo在进程之间, 或者更具体地说是在Mojo的节点之间, 它使用的是平台特定的消息管道。两个节点之间能直接连接, 但这样没有必要, 因为Mojo支持消息路由。网络中的代理节点有一些额外的职责, 它们要设置节点频道和执行沙箱限制的一些操作.
IPC端点本身被称为端口。 在上面的URLLoaderFactory例子中, 客户端和实现方都是以端口作区分。在代码中, 端口是这个样子:
上面的peer_node_name和peer_port_name都是用于寻址的随机128位整数。 如果你向某个端口发送消息, 它将首先将其转发到正确的节点, 然后接收节点会在本地端口的map中查找端口名, 之后再将消息放入正确的消息队列.
当然这意味着, 如果你在浏览器进程中有一个信息泄露漏洞, 你可以泄露端口名并利用它们来将消息插入特权IPC通道。而且实际上, 在Mojo核心文档中的安全章节 中,这一点也被提及到:
"[...]任意节点可以发送任意消息到任意其他节点的任意端口, 只要它知道端口和节点名. [...]因此, 重要的是不要将端口名泄露到不应被授予对应功能(Capability)的节点中."
这里有个不错的例子, 该缺陷能被轻松利用来泄露端口号, 由@NedWilliamson报告的crbug.com/779314。它是一个在blob实现中的整数溢出, 能让你读取浏览器进程中blob前任意数量的堆内存。利用方式大概如下所示:
破坏渲染器.
利用blob缺陷来泄露堆内存.
在内存中搜索端口(一个有效状态+16个高熵字节)
利用泄露的端口将消息插入特权IPC连接.
接下来, 我们来解决两个问题. 如何用CPU缺陷替换掉上面的2和3呢? 以及我们可以通过特权IPC连接获得什么原语(primitives)?
为了使用硬件漏洞来利用这种行为, 我之前在寻找能让你泄露跨进程边界内存的缺陷。MDS攻击中的RIDL似乎是个完美的选择, 因为它能确切做到这点: 它能让你在受影响的CPU上泄露各种内部缓冲区的数据。 具体实现方法可以查看这篇论文或者幻灯片, 它们解释得比我好多了。
确实有为了解决MDS攻击而发布的微代码和系统更新。但是, 如果你阅读过 intel 的有关该主题的Intel's deep dive,文章, 你会注意到在切换到较低特权的运行上下文时, 缓解措施会清除受影响的缓冲区. 如果你的CPU支持超线程, 你仍能够泄露在你物理核心上运行的第二个线程中的数据。 解决这个问题的推荐方法要么是关闭超线程, 要么是实施组调度程序.
你能在网上找到许多用于这个MDS漏洞的PoC, 其中有些在2019年5月份后已经公开了。变种的POC具有以下不同特性:
它们针对loads或stores.
有些要求从L1缓存中清除秘密.
你可以控制从之前的访问中泄露64字节缓存行的索引, 或是64位的值.
速度很大程度上取决于变种和利用程序. 我看过的最高速度的报告是Brandon Falk的MLPDS exploit, 其速度为228kB/s。 与之相比, 我机器上运行简单版的利用程序的速度仅有25kB/s.
所有变种共有的一个特性是, 它们在泄露的内容上具有概率性。然而RIDL 论文描述了一些针对特定值的同步原语, 你通常需要触发对秘密的重复访问才能完全泄露它。
我最终使用不同的MDS变种编写了两个针对Chrome的利用程序, 其中一个针对于使用Xeon Gold 6154处理器的Linux构建, 另外一个是使用Core i7-7600U处理器的Windows。我将描述这两种方法, 因为它们在实践中会遇到不同的挑战.
我的第一个利用程序使用了MFBDS, 针对的是CPU的行填充缓冲区。PoC非常简单:
之后你需要定时访问探测数组来查看哪个索引被缓存了.
你可以在修改开头的0
来控制你泄露的缓存行的偏移量。此外, 你还想对泄露值使用前缀或后缀过滤器, 如论文中描述的那样。 要注意的是, 这里只会泄露不在L1缓存的值, 所以你希望有一种办法能在访问之间把秘密移出缓存。
对于我第一个泄露的目标, 我选择了有特权的URLLoaderFactory。正如前文所述, 渲染器使用URLLoaderFactory来获取网络资源。它会在渲染器上强制启用同源策略(实际上是同站)以确保你无法突破网站平台的限制。 然而, 浏览器进程也会将URLLoaderFactories用于其他的目的, 那些目的有格外的特权。除了忽略同源策略外, 它们也允许上传本地文件。 因此, 如果我们能泄露其中某个的端口名, 我们就能利用它来将 /etc/passwd上传到https://evil.website。
下一步是触发对特权加载程序的端口名的重复访问。让浏览器进程发出网络请求是一种选择, 但似乎开销太大。我决定改为在节点中查找端口。
每个节点都有一个哈希map用于保存所有的本地端口。如果我们向一个不存在的端口发送消息, 目标节点会在map中搜索, 发现它并不存在, 然后丢弃消息。如果我们的端口名和其他的端口名在同一个哈希桶中, 它会读取未知端口的完整哈希并进行比较。这也将端口名自身加载进缓存里, 因为它通常和哈希一样保存在同一个缓存行中。 MFBDS能让我们泄露整个缓存行, 即使其中的值无法直接访问.
在新的Chrome实例上该map的桶大小大概为700, 其会随着渲染器的数目而增长。 这会使得攻击无法进行, 因为这样我们必须得爆破桶索引和缓存行偏移量(多亏了对齐的原因, 有四分之一)。然而, 我注意到一个代码路径可以让你利用service workers创建大量的特权URLLoaderFactories。如果你启用导航预加载创建service worker, 每个顶层的导航会创建这样一个加载程序。通过简单地创建多个iframe并在服务器端停止请求, 你可以同时使数千个加载程序保持活动状态, 并使暴力破解变得更加容易。
唯一漏掉的事是将L1缓存中的目标值赶出去。在实践中, 简单地用32KB数据填充我们的消息似乎可以解决问题, 因为我认为数据将被加载到受害者的L1缓存中并赶出其他所有数据。
总结一下完整的利用流程:
破坏渲染器.
在$NUM_CPU-1 个进程上以不同的缓存行偏移量运行RIDL利用程序.
安装带有导航预加载(navigation preload)的service worker.
创建许多iframes并暂停它们的请求.
向网络进程的随机端口名发送消息.
如果我们能和桶索引发生碰撞, 步骤2中的进程就能泄露端口名.
向URLLoaderFactory发送欺骗消息, 上传本地文件到https://evil.website
在2019年11月, MDS攻击发布了新的变种, 看起来TAA PoC要比我的MFBDS利用快很多。我决定把它移植到Chrome利用上。除此之外, VUSec还发布了一个针对store指令的利用, 如果我们能将秘密写到内存的不同地址, 这应该能让我们避免了缓存刷新的要求。 如果我们能触发浏览器来向特权端口发送消息, 则应该能发生这种情况。 在这种情况下, 秘密端口名也将以节点名称作为前缀, 我们可以使用RIDL论文中的技术轻松对其进行过滤.
我也开始寻找有没有更好的原语, 然后我发现如果我能和NetworkService通信, 它会允许我创建一个新的NetworkContext, 从而选择保存了cookies的sqlite3数据库的文件路径.
为了找到如何从浏览器进程触发消息到NetworkService, 我在接口中寻找可能在渲染器中控制的的IPC方法。NetworkService.OnPeerToPeerConnectionsCountChange吸引到了我, 并且实际上, 这个方法在每次WebRTC连接更新时都会被调用。 你只需要创建一个假的WebRTC连接, 每次你将它标记为连接/断开时, 它会触发到NetworkService的新消息。
一旦我们从破坏的渲染器中泄露了端口名, 我们就得到了能够写入具有完全受控路径的sqlite3数据库的原语。
然而一开始这个听起来不太有用, 你实际上可以利用它来获得代码执行。我注意到Windows批处理文件是非常宽容的文件格式. 如果你在该类型文件的开头有许多垃圾数据, 它会跳过这些数据直到下一个"\r\n", 然后执行那个位置的下一条命令。在我的利用中, 我利用这点在用户的自动运行(autorun)文件夹中创建了cookies.bat文件, 添加了带有"\r\n"和一条命令的cookie, 它会在下一次登录的时候得到执行.
最后, 在我的机器上该利用平均运行时间在1-2分钟, 最长不超过5分钟。我也确信这个速度还能有很大改进, 因为我看到了有许多加快速度的办法, 不论是小的修改还是不同的技术。比如说, MLPDS看起来比我用的变种要快的多。
利用总结:
破坏渲染器。
在$NUM_CPU-1个进程上以不同的缓存行偏移量运行RIDL利用程序。
创建伪造的WebRTC连接, 在连接/断开状态切换。
泄露NetworkService端口名。
创建一个新的NetworkContext写入cookie文件, 位置在c:\path\to\user\autorun\cookies.bat。
插入cookie "\r\ncalc.exe\r\n"。
等待下一次登录。
当我开始研究这个漏洞时, 我很惊讶的发现, 即使漏洞公开了这么长时间, 它还是能够被利用。如果你阅读过有关该主题的指南, 他们通常会在你的操作系统为最新的情况下谈论如何缓解这些漏洞, 并让你注意应禁用超线程以获得完全保护。对于缓解措施的关注的确给了我一种这类漏洞已经被解决的错觉, 而且我认为这些文章可以更清楚地说明启用超线程的影响。
也就是说, 我希望你能从这篇文章中了解到两个事情。第一点是, 信息泄露缺陷可能不只是用来绕过ASLR。即使它并不依赖于秘密端口名, 也会有一些其他有趣的数据泄露, 比如说Chrome的UnguessableTokens, Gmail的cookies或者机器中其他进程的敏感数据。如果你想发现大规模的信息泄露, Chrome或许是个不错的目标。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!