冷硬缓存——通过滥用缓存绕过 RPC 接口安全措施
执行摘要
Akamai 研究人员在 Microsoft Windows RPC 服务中发现了两个重要漏洞,这些漏洞被指定为 CVE-2022-38034 和 CVE-2022-38045 ,基本得分分别为 4.3 和 8.8。
这些漏洞利用了一个设计缺陷,允许通过缓存绕过 MS-RPC 的安全回调。
我们已确认未修补的 Windows 10 和 Windows 11 计算机中存在该漏洞。
漏洞已披露给 Microsoft,并在 10 月 Patch Tuesday 中得到了解决。
Akamai 研究人员开发的自动化工具和方法对漏洞发现过程提供了协助。
我们在 RPC 工具包存储库中提供了我们研究中使用的 漏洞和工具的概念证明。
前言
MS-RPC 是 Windows 操作系统的基石之一。早在上世纪 90 年代发布后,它就已经扎根于该系统的大多数组件中。服务管理器?RPC。Lsass?RPC。COM?RPC。甚至一些针对域控制器的域操作也在使用 RPC。鉴于 MS-RPC 已经变得如此普遍,您会认为,它已经过了大量检查、记录和研究。
其实并非如此。尽管 Microsoft 的 RPC 使用文档相当精良,但关于这个主题的文章却不多,研究人员对 RPC 的研究文章则更少(特别是关于它的安全性方面)。这可能是因为,RPC(不仅仅是 MS-RPC,当然,Microsoft 肯定也是其中的一份子)非常复杂,使得研究和理解成为了一项艰巨的任务。
但我们总是愿意接受挑战,因此,我们决定对 MS-RPC 开展深入研究。不仅仅因为它是一个有趣的研究课题,还因为它的安全影响——即使是现在,常见的攻击技术也依赖于 RPC(T1021.003 借助的是 MS-COM, T1053.005 借助的是 MS-TSCH, T1543.003 借助的是 MS-SCMR,在此仅举几例)。MS-RPC 中内置了安全机制,但如果那里存在漏洞(使得它们会被绕过或滥用,或者让 公开的 RPC 服务遭到滥用,从而以人们不希望看到的方式影响机器),该怎么办?
事实上,我们设法找到了一种通过缓存绕过一个安全机制的方法。通过这种方法,我们发现了一些可以滥用的服务,进而在远程服务器上升级权限,而不需要满足多个必要条件(我们将在后面的文章中对此进行深入探讨)。目前,我们可以分享与潜在漏洞利用的两个真实案例有关的信息,即 WksSvc 和 SrvSvc。在完成其他漏洞的披露过程后,我们将发布与其相关的最新信息。
在这篇博文中,我们将重点讨论 RPC 服务器的安全回调机制、它如何被缓存绕过以及我们如何 将我们的研究自动化, 以将 Windows 服务标记为存在潜在漏洞。我们的自动化工具(以及它们的原始输出)也可以在我们的 RPC 工具包中找到,该工具包将在我们的 GitHub 存储库中共享。我们的存储库还包含其他有用的参考资料的链接,以及我们所依赖的其他研究人员所开展的工作的链接。
安全回调
在我们讨论漏洞本身之前,有必要对 MS-RPC 实施的最基本的安全机制之一(即安全回调)做一些说明。安全回调允许 RPC 服务器开发人员限制对 RPC 接口的访问。它允许他们应用自己的逻辑来允许特定用户的访问权限、实施身份验证或传输类型,或阻止对特定 opnums 的访问(用 opnums 表示服务器公开的函数;即操作编号)。
每次客户端调用服务器上公开的函数时,RPC 运行时都会触发此回调。
在我们的研究中,我们侧重于远程客户端与服务器的交互。我们之所以提到这一点,是因为 RPC 运行时服务器端代码的实施在 ALPC 端点和远程端点(比如命名管道)之间有所不同。
缓存
RPC 运行时实施了安全回调结果缓存,以提高性能和利用率。这本质上意味着,运行时在每次调用安全回调之前都会尝试使用缓存条目。我们来深入了解一下此实施。
在 RPC_INTERFACE::DoSyncSecurityCallback 调用安全回调之前,它首先检查是否存在缓存条目。它通过调用 OSF_SCALL::FindOrCreateCacheEntry 来实现此目的。
OSF_SCALL::FindOrCreateCacheEntry 执行以下操作:
它从 SCALL(一个代表客户端调用的对象)获取客户端的安全上下文。
它从客户端的安全上下文获取缓存字典。
它使用接口指针作为字典的键。值是缓存条目。
如果没有缓存条目存在,它会创建一个。
缓存条目有三个重要字段:接口中过程的数量、位图以及接口世代。
在 RPC 服务器的生命周期中,接口可以更改(例如,如果服务器在现有接口上调用 RpcServerRegisterIf3)。反过来,这又会调用 RPC_INTERFACE::UpdateRpcInterfaceInformation,从而更新接口并增加接口世代。这样一来,缓存知道自己需要“重置”,因为缓存条目可能是来自旧的接口。
缓存机制可以在两种模式下工作:基于接口(这是默认行为)和基于调用。
基于接口的缓存
在这种模式下,缓存基于接口。这意味着,从缓存的角度来看,只要是在同一个接口上,对两个不同函数的调用是没有区别的。
为了知道是否可以使用缓存条目来代替调用安全回调,RPC 运行时会将保存在缓存条目中的接口世代与实际的接口世代进行比较。 由于缓存条目的初始化会将接口世代归零,在第一次进行比较时,接口世代将不同,因此将调用安全回调。如果回调成功返回,RPC 运行时将更新缓存条目的接口世代(因此它将被“标记”为成功的缓存条目 — 允许访问接口,而无需再次调用安全回调)。下次客户端在同一接口上调用函数时,将使用该缓存条目。
基于调用的缓存
当 RPC 接口使用 RPC_IF_SEC_CACHE_PER_PROC 标志注册时,将使用此模式。 在此模式下,缓存基于位图(跟踪安全回调允许访问哪些过程)。因此,如果客户端调用了函数 Foo 并且安全回调成功返回,我们将有一个 Foo 的缓存条目。如果客户端调用 Bar,安全回调将被再次调用。
缓存要求
那么,我们需要具备什么才能使缓存正常运行?首先,我们需要澄清一些术语。MS-RPC 使用一个绑定句柄来表示客户端与服务器之间的逻辑连接。客户端和服务器可以使用指定的函数来操作绑定数据。
可以对绑定进行身份验证。此过程在服务器注册身份验证信息时发生(通过调用 RpcServerRegisterAuthInfo),然后客户端在绑定上设置身份验证信息。这允许服务器检索有关客户端身份的信息。这个身份验证过程的输出是一个为客户端创建的安全上下文对象。
整个缓存机制都基于这个安全上下文。这意味着,如果绑定没有经过身份验证,就不会为客户端创建安全上下文,因此缓存也就无法启用。为了使缓存正常运行, 服务器和客户端都需要注册并设置身份验证信息。
但如果服务器没有注册身份验证信息呢?我们还能启用缓存吗?在此介绍一下:多路复用。
多路复用
在 Windows 10 版本 1703 之前,一个服务可以与其他服务共享同一个 svchost 进程。此行为影响了 MS-RPC 的安全性,因为一些 RPC 运行时对象是在所有接口之间共享的。例如,当注册一个端点(比如 TCP 端口 7777)时,这个端点可以被用来访问在同一进程下运行的所有接口。因此,其他预计在本地访问的服务现在也可被远程访问。Microsoft 也在 此页面 上对此进行了说明。
虽然人们对端点遭到多路复用这一事实已经有了一定的了解和记载, 但我们还想介绍另一个类似的行为:SSPI 多路复用。在注册身份验证信息的过程中,服务器必须指定要使用的身份验证服务。身份验证服务是一个 安全支持提供程序 (SSP),它是一个软件包,可处理从客户端收到的身份验证信息。在大多数情况下,这将是 NTLM SSP、Kerberos SSP 或 Microsoft Negotiate SSP,该服务会在 Kerberos 和 NTLM 之间选择最佳的可用选项。
RPC 运行时在全局范围内保存身份验证信息。这意味着,如果两个 RPC 服务器共享同一个进程,并且其中一个注册了身份验证信息,则另一个服务器也会具有身份验证信息。客户端现在可以在访问每一个服务器的时候对绑定进行身份验证。从安全的角度来看,一些服务器本来没有注册身份验证信息,因此可能不期望客户端对绑定进行身份验证或运行缓存,如今这种情形会迫使它们这样做。
CVE-2022-38045 - srvsvc
借助关于 RPC 安全回调和缓存的新知识,我们开始看看我们是否能在现实生活中实际滥用该机制。我们返回到 srvsvc,过去 我们已经在其中发现了一个 差一漏洞。
Srvsvc 会公开 MS-SRVS 接口。Server 服务(也称为 LanmanServer)是负责管理 SMB 共享的 Windows 服务。共享是可由通用互联网文件系统 (CIFS) 服务器通过网络访问的资源(文件、打印机和目录树)。从本质上讲,网络共享允许用户利用网络上的其他设备来执行各种日常任务。
当我们查看 Srvsvc 的安全回调时,我们注意到该函数可能有另一个漏洞,与我们已经发现的那个漏洞不同。我们来看看安全回调逻辑:
如上所示,srvsvc 的安全回调具有以下逻辑:
如果远程客户端尝试访问 64-73(含)范围内的函数,则拒绝访问
如果非集群帐户的远程客户端尝试访问 58-63(含)范围内的函数,则拒绝访问
因此,从本质上讲,远程客户端会被阻止访问接口的这些特定函数。此范围检查提示受限制的函数存在敏感因素,应仅由预期的(本地)进程调用。
尽管此检查试图阻止对这些函数的远程访问,但远程攻击者可以通过滥用缓存来绕过此检查。首先,远程攻击者调用一个不在此范围内的函数,即一个远程可用的函数。由于安全回调函数返回 RPC_S_OK,RPC 运行时会将此结果作为一个成功的结果进行缓存。由于接口没有使用 RPC_IF_SEC_CACHE_PER_PROC 标志注册,因此缓存将基于接口。因此,下次攻击者在同一接口上调用 任何 函数时,将使用该缓存条目并允许访问。这意味着,攻击者现在可以调用他们不应具有访问权限的本地函数,而安全回调根本就不会被调用。
Srvsvc 不注册身份验证信息,因此,在正常情况下,客户端无法对绑定进行身份验证,因此缓存也就无法启用。事实证明,当服务器机器的内存小于 3.5 GB 时, Srvsvc 与其他服务共享同一个 svchost 进程。“AD Harvest Sites and Subnets Service”和“Remote Desktop Configuration service”服务 可注册身份验证信息,因此,srvsvc 现在容易受到缓存攻击。
在这种特定情况下,攻击者可以访问 opnums 为 58-74 的受限制函数。攻击者可以利用这些函数做的其中一件事是 强迫进行远程机器身份验证。
寻宝
在了解到滥用安全回调的缓存机制会产生实际的漏洞之后, 我们决定尝试找到其他可能会受到缓存攻击的接口。但手动找到所有接口将是一项漫长而艰巨的任务,因此我们想找到一种方法来自动执行此过程。
在寻找 RPC 接口时,我们可以采取两种方法:通过当前运行的进程或通过文件系统。
通过运行进程,我们可以查看已经加载在内存中的 RPC 服务器,可以通过查询远程端点映射器在远程服务器上执行此操作(例如,使用 Impacket 的 rpcmap 或 rpcdump),也可以在本地执行此操作(使用 RpcView 或 RpcEnum等工具)。不过,这种方法有一个问题: 我们会遗漏当前没有加载的所有接口,而且我们无法查看客户端接口,因为它们没有注册。
另外,我们可以抓取 Windows 文件系统,寻找在其中编译的 RPC 接口。对于每个接口,我们通过分析传递给 RpcServerRegisterIf 的参数 来解析其注册信息。这与 RpcEnum 中的方法类似,但我们抓取的是文件系统,而不是内存。
在我们的研究中,我们选择了文件系统方法,以便囊括那些 不一定在内存中加载的接口。我们编写了各种脚本和工具来实现这一过程的自动化,这些脚本和工具可以在我们的 RPC 工具包存储库中找到。
为了找到启用了缓存的接口,我们不需要解析 RPC 接口本身——所有需要的信息都可以从 RPC 服务器注册调用中提取。注册函数接受 RPC 接口结构、注册标志和安全回调指针。尽管如此,解析 RPC 接口结构仍然可以提供有用的信息,比如接口公开的函数,或者它是否被 RPC 服务器或客户端使用。尽管我们主要对 RPC 服务器感兴趣(其中可能存在漏洞),但 RPC 客户端提供了与调用服务器有关的良好见解,我们可以参考这些见解以进行漏洞利用。
RPC 服务器接口结构已 记录在案,因此我们不必猜测它的字段。另外,大小字段和传输语法是不变的(实际上有两种可能的传输语法:DCE NDR 和 NDR64,但我们只是偶然使用了 DCE NDR)。
通过寻找这两个常量(使用 Yara 或正则表达式),找到所有 RPC 接口结构是一件十分简单的事情。找到后,我们可以使用 解释器信息字段 来查看服务器实施的功能。
但我们仍然缺乏关于接口的安全回调(如果存在)和安全回调是否被缓存的信息。为此,我们必须求助于我们值得信赖的朋友(反汇编程序)。每个自我尊重的反汇编程序都有一个 xref 功能,因此在 RPC 服务器中找到所有接口注册函数调用是很容易的事情。然后,我们只需解析函数调用参数以提取接口结构地址(这样我们就可以将其与我们抓取的 RPC 服务器数据进行交叉引用)、安全回调地址(如果存在)以及 RPC 接口标志。
我们已经发布了我们的抓取脚本,它们的作用正是这个;可以在我们的 RPC 工具包中找到这些脚本,同时还包括 它们 在 Windows Server 2012 和 Server 2022 中的输出。
CVE 或不发生
所有这些方法和理论都很好,但它们真的能产生成果吗?
答案是 肯定的。 超过 120 个接口同时具有安全回调和缓存功能,其中很多接口没有记录在案。这本身并不是恐慌的理由,因为大多数时候安全回调不会受到缓存的太多影响。通常情况下,安全回调所做的检查是针对不可缓存的值进行的,比如传输协议序列(例如 TCP)或身份验证级别。任何变更都需要一个新的安全上下文,因为必须建立一个新连接,这会重置缓存并使任何可能的缓存绕过行为失效。
通过这种研究方法,我们发现了一些漏洞。目前我们只能讨论其中一个,因为其余的还在披露过程中。
WksSvc
CVE-2022-38034 CVSS 评分:4.3
WksSvc 会公开 MS-WKST 接口。该服务负责管理域成员资格、计算机名称和到 SMB 网络重定向器的连接,比如 SMB 打印机服务器。通过查看接口的安全回调,我们可以看到,有几个函数的处理方式与其他的不同。
opnum 在 8-11 之间的函数也被选为由本地客户端调用,这意味着不允许远程调用它们。但是,由于我们拥有缓存,如果我们先调用一个允许远程调用的不同函数,然后再调用其中一个受限制的函数,情况会怎样?
您猜对了:我们可以远程调用本地受限的函数,因为第一个调用的结果已缓存。现在的问题是:这些函数是否重要到必须保证它们只被限制给本地客户端调用?
公开的函数包括 NetrUseAdd、 NetrUseGetInfo、 NetrUseDel和 NetrUseEnum。如果它们听起来很熟悉,那是因为可以通过 netapi32.dll 访问它们(例如, 请参见 NetUseAdd)。
这很好,因为它给了我们一个线索,让我们知道我们可以用这种攻击做什么。也就是说,我们可以把远程服务器连接到我们选择的网络共享文件夹,甚至可以把它映射到我们选择的逻辑驱动器盘符上(类似于网络使用)。(巧合?也许不是。)
这给了我们两种攻击方案:
1.我们可以要求对我们的共享文件夹进行身份验证;然后我们可以把它中继到不同的服务器,进行 NTLM 中继攻击,或者存储令牌并离线破解密码。
2.或者我们可以用有趣或有用的文件来伪装现有的文件服务器(或假装是新的文件服务器)。由于这些文件在我们的控制之下,我们可以按照我们认为合适的方式将其作为攻击武器,希望它们能让我们感染目标用户。
这两种情况(以及能够远程调用本地限制的函数的能力)足以让 Microsoft 把这个漏洞归类为 EoP(评分为 4.3)。
但这并不是全部——我们仍有一些需要克服的问题。
安全上下文
WksSvc 下的 RPC 服务器本身并不执行任何身份验证注册。如果服务是独立运行的,则无法进行客户端身份验证(会导致错误 RPC_S_UNKNOWN_AUTHN_SERVICE)。因此,我们需要让该服务与其他服务一起运行,以便同时滥用 SSPI 多路复用。 这将受影响的 Windows 版本 限制为 Windows 10 版本 1703 之前的版本,或运行内存小于 3.5 GB 的较新版本。
登录会话
另一个问题(即网络映射文件夹的工作方式所存在的问题)在于,这些文件夹被限制在 创建它们的 用户的登录会话中。因为我们首先需要登录以获得安全绑定和缓存,这意味着,我们将始终创建一个与目标机器上现有(交互式)会话不同的登录会话。 实际上,这意味着我们的漏洞没有产生影响。我们创建的网络映射位于我们短暂的登录会话下(而不是普通用户登录机器时创建的会话),因此它不可见。
为了克服这个问题,我们不得不对 NetrUseAdd的代码进行更深入的挖掘。事实证明,我们可以向 NetrUseAdd 传递一些标记,指示它在“全局”域名空间创建映射,这将影响所有用户。甚至可以在可用的标头文件 LMUse.h 中找到这些标记:
借助这些标志,我们的代码现在可成功创建一个全局映射,这将影响交互式会话,从而完成我们的漏洞利用尝试。
总结
MS-RPC 是一个庞大而复杂的协议。它也为 Windows 的一些核心功能提供服务。虽然它有一些安全功能(开发人员可以用来保护他们的 RPC 服务器),它仍是安全研究人员的一个有趣话题,因为它包含一个可能产生安全影响的漏洞。
尽管如此,关于这个话题的公开研究还不多。在这篇博文中,我们探讨了 MS-RPC 中的一个大型安全机制(安全回调),并发现了一种以回调结果缓存的形式开展的绕过操作。我们还详细介绍了我们寻找存在漏洞的 RPC 服务器的研究方法,并通过漏洞报道展示了我们的一些发现。
我们希望这篇文章及其附带的 RPC 工具包存储库能够帮助其他人研究 RPC 服务器和安全机制。