在我上一篇博客文章中,我讨论了Windows内核对受限令牌的处理问题,该问题允许我逃离Chrome GPU沙箱。最初,我计划使用Firefox进行概念验证,因为Firefox对其内容渲染器使用与Chrome GPU进程相同的有效沙箱级别。这意味着FF内容RCE会在沙箱中执行代码,在沙箱中您可能会滥用Windows内核受限令牌问题,这会使问题变得更加严重。
然而,在研究沙箱转义时,我意识到这是FF最不担心的问题。尽管Windows问题得到了修复,但将GPU级别的沙箱用于多个进程引入了沙箱转义向量。这篇博客是关于Chromium沙箱的具体行为以及FF易受攻击的原因。我还将详细介绍我对Chromium沙箱所做的更改,以介绍一种缓解该问题的方法,Mozilla使用该方法来修复我的报告。
供参考的P0期是2016年,FF期是1618911。FF在本页定义他们自己的沙箱配置文件。在撰写本文时,内容沙箱被定义为5级,因此我将继续引用L5,而不是GPU沙箱。
问题的根本原因是,使用L5,一个内容进程可以打开另一个进程进行完全访问。在Chromium派生的浏览器中,这通常不是问题,一次只有一个GPU进程在运行,尽管可能会有其他非Chromium进程同时运行,这些进程可能是可访问的。Chromium中的内容呈现器使用的沙箱明显受到更多限制,他们应该不能打开任何其他进程。
L5沙箱使用受限令牌作为主要沙箱实施。一个内容进程可以访问另一个内容进程的原因在于该进程的主令牌的默认DACL。对于内容进程,RestrictedToken::GetRestrictedToken中设置的默认DACL向以下用户授予完全访问权限:
默认DACL用于设置初始进程和线程安全描述符。L5使用的令牌级别是USER_LIMITED,这将禁用除以下组之外的几乎所有组:
将所有这些结合在一起,当前用户组和受限受限SID的组合将导致授予对沙箱进程或线程的完全访问权限。
要理解为什么能够打开另一个内容进程是这样一个问题,我们必须了解Chromium沙箱是如何引导一个新进程的。由于将主令牌分配给新进程的方式,一旦进程启动,就不能再为其他令牌更改它。您可以执行一些操作,例如删除权限和删除完整性级别,但不能删除组或添加新的受限SID。
新的沙箱进程需要进行一些初始预热,这可能需要比授予受限沙箱令牌更多的访问权限,因此Chromium使用了一个技巧。它为初始线程分配一个更有特权的模拟令牌,以便预热以更高的权限运行。对于L5,初始令牌的级别是USER_RESTRITED_SAME_ACCESS,它只创建一个受限制的令牌,没有禁用的组,所有普通组都被添加为受限制的SID。这使得令牌几乎等同于普通令牌,但被认为是受限的。如果主令牌受到限制,但模拟令牌不受限制,Windows将阻止设置令牌。
通过调用沙箱目标服务中的LowerToken函数,一旦完成所有预热,就会删除模拟令牌。这意味着调用LowerToken时有一个新沙箱进程开始的时间窗口,除了IL较低外,该进程实际上是未沙箱运行的。如果您可以在删除模拟之前劫持执行,您可以立即获得足以逃离沙箱的权限。
与Chrome GPU进程不同,FF会在正常使用期间定期生成一个新的内容进程。只需创建一个新选项卡就可以生成一个新流程。因此,一个受损的内容进程只需等待,直到创建新的进程,然后立即劫持它。几乎可以肯定,受损的呈现器可以通过IPC调用强制创建新进程,但我没有进一步研究这一点。
有了这些知识,我使用许多与上一篇博客文章中相同的技术开发了一个完整的POC。USER_RESTRITED_SAME_ACCESS内标识的较高权限简化了攻击。例如,我们不再需要劫持COM服务器的线程,因为更有特权的令牌允许我们直接打开进程。此外,至关重要的是,我们永远不需要离开受限沙盒,因此利用漏洞不会依赖于针对前一个问题修复的内核错误MS。您可以找到该问题的完整POC,我已在下图中总结了这些步骤。
在我的报告中,我建议对该问题进行修复,在沙箱策略中启用SetLockdownDefaultDacl选项。SetLockdownDefaultDacl从默认DACL中删除受限和登录SID,这将阻止一个L5进程打开另一个。我添加这个沙箱策略函数是为了响应我在上一篇博客中提到的GPU沙箱转义,它由Pwn2Own的lokihardt使用。然而,其目的是阻止GPU进程打开渲染器进程,而不是阻止一个GPU进程打开另一个进程。因此,该策略不是在GPU沙箱上设置的,而是仅在渲染器上设置的。
事实证明,我不是第一个报告一个FF内容进程能够打开另一个FF内容进程的人。尼克拉斯·鲍姆斯塔克在我的报告前一年就报告了这件事。我建议的修复,启用SetLockdownDefaultDacl已经在修复Niklas的报告中尝试过,它破坏了各种东西,包括DirectWrite缓存和音频播放,以及使应用SetLockdownDefaultDacl变得不可取的重大性能回归。DirectWrite缓存中断等问题的原因是Windows RPC服务中的典型编码模式,如下所示:
此示例代码在特权服务中运行,并由沙箱应用程序通过RPC调用。它首先调用RPC运行时来查询调用者的进程ID,然后模拟调用者并尝试打开调用进程的句柄。如果打开进程失败,则RPC调用返回拒绝访问错误。
对于正常应用程序,完全合理的假设是调用方可以访问其自己的进程。然而,一旦我们锁定了流程安全,情况就不再是这样了。如果我们阻止对同一级别的其他进程的访问,那么结果是我们也会阻止打开我们自己的进程。通常这不是问题,因为进程内的大多数代码都使用当前进程伪句柄,该句柄从未经过访问检查。
尼克拉斯的报告并没有提供完整的沙盒逃生。由于缺乏完整的POC,再加上修复的困难,导致修复停滞。然而,随着完整的沙箱逃逸展示了该问题的影响,Mozilla将不得不在性能或安全性之间做出选择,除非可以实施另一个修复程序。因为我是Chromium提交者,也是Windows沙箱的所有者,所以我意识到我可能比依赖我们代码的Mozilla更适合修复这个问题。
在没有任何管理员权限的情况下,许多角度,例如内核进程回调,对我们是不可用的。修复程序必须完全处于具有普通用户权限的用户模式。
修复的关键是受限SID列表可以包括令牌的现有组中不存在的SID。我们可以为每个沙箱生成一个随机的SID,该进程既可以作为受限的SID添加,也可以添加到默认的DACL中。然后,我们可以使用SetLockdownDefaultDACL锁定默认DACL。
打开进程时,访问检查将与用于普通检查的当前用户SID和用于受限SID检查的随机SID相匹配。这也将在RPC上工作。但是,每个内容进程将具有不同的随机SID,因此尽管正常检查仍将通过,但访问检查无法成功通过受限SID检查。这达到了我们的目标。您可以在PolicyBase::MakeTokens中检查实现。
我将补丁添加到Chromium库中,FF可以将其合并并进行测试。它阻止了攻击载体,而且似乎没有引入之前的性能问题。我说,“似乎”,这类更改的问题之一是,不可能确切地知道某些RPC服务或其他代码不依赖于特定的行为来执行更改所破坏的功能。然而,这段代码现在已经在FF76中发布了,所以如果有问题,毫无疑问它会变得很明显。
修复的另一个问题是它的选择加入,为了安全,系统上的每个其他进程都必须选择加入缓解,包括所有Chromium浏览器以及Chromium的用户,如Electron。例如,如果Chrome没有更新,那么FF内容进程可能会杀死Chrome的GPU进程,这将导致Chrome重新启动它,FF进程可以通过劫持新的GPU进程通过Chrome逃脱。这就是为什么,尽管不是直接易受攻击,我还是在2020年4月底发布的M83(和Microsoft Edge83)中启用了Chromium GPU进程的缓解。
总之,这篇博文演示了FF中的沙箱转义,这需要在Chromium沙箱中添加一个新功能。与前一篇博文形成对比的是,可以在不需要更改FF或Chromium无法访问的Windows代码的情况下修复该问题。也就是说,我们很可能很幸运,可以在不破坏任何重要东西的情况下做出改变。下一次可能就不那么容易了。