如何保护 Windows RPC 服务器,以及如何不保护。
PetitPotam技术在人们的脑海 中仍然记忆犹新。虽然它不是直接的利用,但它是一个有用的步骤,可以从特权帐户获取未经身份验证的 NTLM 以转发到 AD CS Web 注册服务之类的东西以破坏 Windows 域。有趣的是,在微软最初对修复这些问题不屑一顾之后,他们发布了一个修复程序,尽管在撰写本文时似乎还不够。
虽然有很多关于如何滥用 EFSRPC 接口的详细信息,但对于为什么它可以被利用的原因却很少。我认为最好快速了解 Windows RPC 接口是如何保护的,然后进一步了解为什么可以使用未经身份验证的EFSRPC接口。
警告:毫无疑问,我可能会遗漏 RPC 中的其他安全检查,这些是我所知道的主要安全检查 :-)
RPC 服务器安全
RPC 的服务器安全性似乎是随着时间的推移而建立起来的。因此,有多种方法可以做到这一点,有些方法比其他方法更好。基本上有三种方式,可以混搭:
- 保护端点
- 保护接口
- 临时安全
让我们依次来确定每个人如何保护 RPC 服务器。
保护端点
您使用RpcServerUseProtseqEp API注册 RPC 服务器将侦听的端点 。此 API 采用端点类型,例如ncalrpc (ALPC)、ncacn_np (命名管道) 或ncacn_ip_tcp (TCP 套接字)并创建侦听端点。例如,以下将创建一个名为DEMO的命名管道端点。
代码语言:javascript复制RpcServerUseProtseqEp(
L"ncacn_np",
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
L"\pipe\DEMO",
nullptr);
最后一个参数是可选的,但表示您分配给端点以限制谁具有访问权限的安全描述符 (SD)。这只能在 ALPC 和命名管道上强制执行,因为 TCP 套接字之类的东西在连接时(技术上)没有访问检查。如果您未指定 SD,则会分配默认值。对于命名管道,默认 DACL 授予以下用途写访问权限:
- 每个人
- NT AUTHORITY匿名登录
- 自己
其中SELF是创建用户的 SID。这是一个相当宽松的 SD。关于 RPC 端点的一件有趣的事情是它们是多路复用的。您没有明确地将端点与要访问的 RPC 接口相关联。相反,您可以连接到进程创建的任何端点。最终结果是,如果同一进程中存在安全性较低的端点,则可能使用最不安全的端点访问接口。一般来说,这使得依赖端点安全存在风险,尤其是在运行多个服务的进程中,例如 LSASS。无论如何,如果您想使用 TCP 端点,则不能依赖端点安全性,因为它不存在。
保护接口
保护 RPC 服务器的下一个方法是保护接口本身。您使用以下 API 之一注册 MIDL 生成的接口结构:
- RpcServerRegisterIf
- RpcServerRegisterIf2
- RpcServerRegisterIfEx
- RpcServerRegisterIf3
- RpcServerInterfaceGroupCreate
每个都有不同数量的参数,其中一些参数决定了接口的安全性。最新的 API 是 Windows 8 中引入的RpcServerRegisterIf3和 RpcServerInterfaceGroupCreate 。后者只是在一次调用中注册多个接口的一种方式,所以我们只关注前者。RpcServerRegisterIf3具有三个影响安全性的 参数, SecurityDescriptor、 IfCallback和 Flags。
SecurityDescriptor参数最 容易解释。它为接口分配一个 SD,当在该接口上进行调用时,调用者的令牌会根据 SD 进行检查,并且只有在检查通过时才授予访问权限。如果未指定 SD,则使用默认值,授予以下 SID 访问权限(假设是非 AppContainer 进程)
- NT AUTHORITY匿名登录
- 每个人
- NT AUTHORITYRETRICTED
- 内置管理员
- 自己
用于访问检查的令牌基于客户端的身份验证(我们稍后将讨论)或端点的身份验证。ALPC 和命名管道是经过身份验证的传输,而 TCP 不是。当使用未经身份验证的传输时,访问检查将针对匿名令牌。这意味着如果 SD 不包含允许 匿名登录的 ACE,它将被阻止。
请注意,由于访问检查过程的怪癖,如果调用者授予任何访问权限,而不是特定访问权限,则 RPC 运行时会授予访问权限。这意味着如果调用者被认为是所有者,通常设置为创建用户 SID,他们可能只被授予 READ_CONTROL 但这足以绕过检查。如果调用者具有 SeTakeOwnershipPrivilege或类似权限,这也可能很有用,因为它可以一般地绕过接口 SD 检查(当然,该权限本身就是危险的)。
第二个参数IfCallback采用RPC_IF_CALLBACK函数指针。这个回调函数会在调用接口时被调用,虽然它会在检查 SD 之后被调用。如果回调函数返回 RPC_S_OK那么调用将被允许,其他任何东西都会拒绝调用。回调获取指向接口和绑定句柄的指针,并且可以进行各种检查以确定是否允许调用者访问接口。
一个常见的检查是客户端的 身份验证级别。当使用RpcBindingSetAuthInfo API连接到服务器时,客户端可以指定要使用的级别, 但是服务器不能直接指定它接受的最低身份验证级别。相反,回调可以使用RpcBindingInqAuthClient API 来确定客户端使用的内容并基于此授予或拒绝访问。我们通常关心的认证级别如下:
- RPC_C_AUTHN_LEVEL_NONE - 无身份验证
- RPC_C_AUTHN_LEVEL_CONNECT - 在连接时进行身份验证,但不是每次调用。
- RPC_C_AUTHN_LEVEL_PKT_INTEGRITY - 连接时的身份验证,每个调用都有完整性保护。
- RPC_C_AUTHN_LEVEL_PKT_PRIVACY - 连接时的身份验证,每个调用都被加密并具有完整性保护。
身份验证是使用定义的身份验证服务实现的,例如 NTLM 或 Kerberos,尽管这对于我们的目的并不重要。另请注意,这仅用于通过远程协议(如命名管道或 TCP)提供的 RPC 服务。如果 RPC 服务器在 ALPC 上侦听,则假定它始终是 RPC_C_AUTHN_LEVEL_PKT_PRIVACY。服务器可以做的其他检查是客户端使用的协议序列,这将允许通过 TCP 拒绝访问但允许命名管道。
最后一个参数是标志。与安全性最明显相关的标志是 RPC_IF_ALLOW_SECURE_ONLY (0x8)。如果当前身份验证级别是RPC_C_AUTHN_LEVEL_NONE,这会阻止对接口的访问 。这意味着调用者必须能够使用允许的身份验证服务之一对服务器进行身份验证。至少在任何现代版本的 Windows 上,使用 NULL 会话是不够的。当然,这并没有说明谁已经进行了身份验证,服务器可能仍想检查调用者的身份。
另一个重要的标志是 RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH (0x10)。如果服务器指定了安全回调并且未设置此标志,则任何未经身份验证的客户端将被自动拒绝。
如果这还不够复杂,那么至少还有一个其他相关设置适用于系统范围,它将确定什么类型的客户端可以访问什么 RPC 服务器。限制未经身份验证的RPC 客户端 组策略。默认情况下,如果 RPC 服务器在 Windows 的服务器 SKU 上运行并且在客户端 SKU 上经过身份验证,则此设置为无。
通常,此策略的作用是限制客户端在未单独验证到有效身份验证级别时是否可以使用未经身份验证的传输,例如 TCP。当设置为None时,可以通过未经身份验证的传输访问 RPC 服务器,但受接口注册的任何其他限制的约束。如果设置为 Authenticated,则拒绝未经身份验证的传输调用,除非为接口设置了 RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH标志或客户端已单独进行身份验证。还有第三个选项,Authenticated without exceptions,如果调用者没有使用经过身份验证的传输,它将在所有情况下阻止调用。
临时安全
最后的检查类型基本上是服务器为验证调用者所做的任何其他事情。一种常见的方法是在接口上的特定功能内执行检查。例如,服务器通常可以允许未经身份验证的客户端,除非调用方法来读取重要的秘密值。此时可以插入身份验证级别检查以确保客户端已在 RPC_C_AUTHN_LEVEL_PKT_PRIVACY进行身份验证,以便在返回给客户端时将加密密钥。
最终,您必须检查您感兴趣的每个功能,以确定是否进行了安全检查(如果有的话)。与所有临时检查一样,其中可能存在逻辑错误,可被利用以绕过安全限制。
深入研究 EFSRPC
好的,这涵盖了如何保护 RPC 服务器的基础知识。下面看一下 PetitPotam 滥用的 EFSRPC 服务器的具体例子。奇怪的是,RPC 服务器有两种实现,一种在efslsaext.dll中,其接口 UUID 为 c681d488-d850-11d0-8c52-00c04fd90f7e,另一种在 efssvc.dll 中,接口 UUID 为 df1941c5-fe89-4e79-bf10-463657acf44d。efslsaext.dll中的那个是未经身份验证即可访问的,所以让我们从那里开始。我们将通过三种方法来保护服务器以确定它在做什么。
首先,服务器不注册任何自己的协议序列,无论是否使用 SD。这意味着谁可以调用 RPC 服务器取决于托管进程注册了哪些其他端点,在本例中是 LSASS。
其次,检查对 RPC 服务器接口注册函数之一的调用,在 InitializeLsaExtension中有一个对 RpcServerRegisterIfEx的调用。这允许调用者指定安全回调而不是 SD。但是在这种情况下,它没有指定任何安全回调。InitializeLsaExtension函数也没有指定两个安全标志中的任何一个(它设置 没有 任何安全影响的RPC_IF_AUTOLISTEN )。这意味着通常允许任何经过身份验证的调用者。
最后,从特别安全的角度来看,所有主要函数(例如 EfsRpcOpenFileRaw)都调用函数 EfsRpcpValidateClientCall ,该函数类似于以下内容(已删除错误检查)。
代码语言:javascript复制DWORD AllowOpenRawDL = 0;
RegGetValueW(
HKEY_LOCAL_MACHINE,
L"SYSTEM\CurrentControlSet\Services\EFS",
L"AllowOpenRawDL",
RRF_RT_REG_DWORD | RRF_ZEROONFAILURE,
NULL,
&AllowOpenRawDL);
if (AllowOpenRawDL == 1 &&
!EfsRpcpValidateClientCall(hBinding, &ValidClient) && ValidClient) {
// Call allowed.
}
void EfsRpcpValidateClientCall(RPC_BINDING_HANDLE Binding,
PBOOL ValidClient) {
unsigned int ClientLocalFlag;
I_RpcBindingIsClientLocal(NULL, &ClientLocalFlag);
if (!ClientLocalFlag) {
RPC_WSTR StringBinding;
RpcBindingToStringBindingW(Binding, &StringBinding);
RpcStringBindingParseW(StringBinding, NULL, &Protseq,
NULL, NULL, NULL);
if (CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE,
Protseq, -1, L"ncacn_np", -1) == CSTR_EQUAL)
*ValidClient = TRUE;
}
}
}
基本上,只有在调用者使用命名管道传输并且管道没有在本地打开时, ValidClient参数才会设置为TRUE,即命名管道是通过 SMB 打开的。这基本上是所有正在检查的安全性。因此,唯一可以实施的安全性受到允许谁连接到合适的命名管道端点的限制。
LSASS 至少注册pipelsass 命名管道端点。在lsasrv.dll中设置时,为命名管道定义了一个 SD,该命名管道授予以下用户访问权限:
- 每个人
- NT AUTHORITY匿名登录
- 内置管理员
因此理论上匿名用户可以访问管道,并且在接口定义中没有其他安全检查。现在通常匿名访问默认情况下不会通过 NULL 会话授予命名管道,但是域控制器通过配置的网络访问对此策略有一个例外:可以匿名访问的命名管道安全选项。对于 DC,这允许匿名访问lsarpc、samr和netlogon管道,它们都是lsass管道的别名。
您现在可以理解为什么在 DC 上可以匿名访问 EFS RPC 服务器。其他 EFS RPC 服务器如何阻止访问?在这种情况下,它指定了一个接口 SD 来限制只有Everyone组和BUILTINAdministrators的访问权限。默认情况下,匿名用户不是每个人的成员(尽管可以这样配置),因此即使您通过lsass管道连接,这也会阻止访问。
修复在
微软为修复PetitPotam做了什么?他们绝对没有做的一件事是更改接口注册或命名管道端点安全性。相反,他们向EfsRpcOpenFileRaw添加了额外的临时检查 。具体来说,他们添加了以下代码:
代码语言:javascript复制DWORD AllowOpenRawDL = 0;
RegGetValueW(
HKEY_LOCAL_MACHINE,
L"SYSTEM\CurrentControlSet\Services\EFS",
L"AllowOpenRawDL",
RRF_RT_REG_DWORD | RRF_ZEROONFAILURE,
NULL,
&AllowOpenRawDL);
if (AllowOpenRawDL == 1 &&
!EfsRpcpValidateClientCall(hBinding, &ValidClient) && ValidClient) {
// Call allowed.
}
基本上,除非 AllowOpenRawDL注册表值设置为 1,否则无论身份验证客户端如何,都将完全阻止调用。这似乎是一个完全有效的修复,除了 EfsRpcOpenFileRaw不是唯一可用于启动 NTLM 身份验证会话的函数。正如Lee Christensen所指出的,您也可以通过 EfsRpcEncryptFileSrv或 EfsRpcQueryUsersOnFile或其他方式进行操作。因此,由于没有进行其他更改,因此这些其他功能与原始功能一样未经身份验证即可访问。
真的不清楚微软是如何没有看到这一点的,但我想他们可能已经被他们蒙蔽了,他们实际上修复了他们坚持认为是系统管理员必须处理的配置问题。
2021 年 8 月 17 日更新:值得注意的是,虽然您可以未经身份验证访问其他功能,但似乎任何网络访问都是使用“经过身份验证的”调用者(即匿名用户)完成的,因此它可能没那么有用。这个博客的重点不是滥用 EFSRPC,而是为什么它是可滥用的 :-)