绕过UAC的最复杂方法:Kerberos协议滥用技术解析

本文详细解析了如何通过滥用Kerberos协议中的KERB-AD-RESTRICTION-ENTRY和KERB-LOCAL机制来绕过Windows UAC保护,涉及LSASS处理逻辑、服务票证操作和机器ID验证等核心技术细节。

绕过UAC的最复杂方法:Kerberos协议滥用技术解析

虽然我不常研究这个方向,但发现新的UAC绕过方法总是很有趣。在阅读Rubeus工具的一些功能时,我意识到存在一种可能通过滥用Kerberos来绕过UAC的方法——至少在域加入系统上可行。不清楚这是否曾被记录过,某篇帖子似乎讨论了类似内容,但依赖从另一系统执行UAC绕过,而我将描述的方法在本地工作。即使该技术曾被描述过,我也不确定其底层原理已被文档化。

背景知识

让我们从系统如何阻止你绕过这个最无用的安全功能开始。默认情况下,LSASS会过滤任何网络身份验证令牌,以移除本地管理员的权限。但有一个重要例外:如果用户是域用户且是本地管理员,LSASS将允许网络身份验证使用完整的管理员令牌。如果你在本地使用Kerberos进行身份验证,这会是一个问题。这不会是一个简单的UAC绕过吗?只需作为域用户向本地服务进行身份验证,你就会获得网络令牌,从而绕过过滤?

不,Kerberos有特定的附加机制来阻止这种攻击向量。如果我要客气点,我会说这种行为也确保了一定程度的安全性。如果你没有以管理员令牌运行,那么访问SMB环回接口不应该突然授予你管理员权限,通过该权限你可能意外破坏系统。

去年一月,我读了微软Steve Syfuhs的一篇帖子,关于Kerberos如何防止这种本地UAC绕过。简而言之:当用户想要获取服务的Kerberos票证时,LSASS会向KDC发送TGS-REQ请求。在请求中,它会嵌入一些表明用户是本地的安全信息。该信息将嵌入生成的票证中。当该票证用于向同一系统进行身份验证时,Kerberos可以提取信息并检查是否匹配已知信息。如果匹配,它会利用该信息意识到用户未提升权限,并相应过滤令牌。不幸的是,尽管我喜欢Steve的帖子,但这篇尤其缺乏细节。我猜我得自己追踪其工作原理。

让我们转储Kerberos票证的内容,看看是否能找到票证信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
PS> $c = New-LsaCredentialHandle -Package 'Kerberos' -UseFlag Outbound
PS> $x = New-LsaClientContext -CredHandle $c -Target HOST/$env:COMPUTERNAME
PS> $key = Get-KerberosKey -HexKey 'XXX' -KeyType AES256_CTS_HMAC_SHA1_96 -Principal $env:COMPTUERNAME
PS> $u = Unprotect-LsaAuthToken -Token $x.Token -Key $key
PS> Format-LsaAuthToken $u
<KerberosV5 KRB_AP_REQ>
Options         : None
<Ticket>
Ticket Version  : 5
...
<Authorization Data - KERB_AD_RESTRICTION_ENTRY>
Flags           : LimitedToken
Integrity Level : Medium
Machine ID      : 6640665F...
<Authorization Data - KERB_LOCAL>
Security Context: 60CE03337E01000025FC763900000000

我高亮了两个有趣的部分:KERB-AD-RESTRICTION-ENTRY和KERB-LOCAL条目。当然,我不是猜出这些名称的,它们在Microsoft Kerberos协议扩展(MS-KILE)规范中有部分文档。KERB_AD_RESTRICTION_ENTRY显然最有趣,它包含“LimitedToken”和“Medium Integrity Level”字样。

当通过SSPI从网络客户端接受Kerberos AP-REQ时,LSASS中的Kerberos模块会调用LSA函数LsaISetSupplementalTokenInfo,以在需要时应用来自KERB-AD-RESTRICTION-ENTRY的信息到令牌。相关代码大致如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
NTSTATUS LsaISetSupplementalTokenInfo(PHANDLE phToken, 
                        PLSAP_TOKEN_INFO_INTEGRITY pTokenInfo) {
  // ...
  BOOL bLoopback = FALSE:
  BOOL bFilterNetworkTokens = FALSE;
  if (!memcmp(&LsapGlobalMachineID, pTokenInfo->MachineID,
       sizeof(LsapGlobalMachineID))) {
    bLoopback = TRUE;
  }
  if (LsapGlobalFilterNetworkAuthenticationTokens) {
    if (pTokenInfo->Flags & LimitedToken) {
      bFilterToken = TRUE;
    }
  }
  PSID user = GetUserSid(*phToken);
  if (!RtlEqualPrefixSid(LsapAccountDomainMemberSid, user)
    || LsapGlobalLocalAccountTokenFilterPolicy 
    || NegProductType == NtProductLanManNt) {
    if ( !bFilterToken && !bLoopback )
      return STATUS_SUCCESS;
  }
  /// Filter token if needed and drop integrity level.
}

我高亮了该函数中的三个主要检查:第一个比较KERB-AD-RESTRICTION-ENTRY的MachineID字段是否与LSASS中存储的匹配。如果是,则设置bLoopback标志。然后检查一个据我所知未文档化的LSA标志以过滤所有网络令牌,此时它会检查LimitedToken标志并相应设置bFilterToken标志。此过滤模式默认关闭,因此通常bFilterToken不会被设置。

最后,代码查询当前创建的令牌SID,并检查以下任一条件是否为真:

  • 用户SID不是本地帐户域的成员。
  • LocalAccountTokenFilterPolicy LSA策略非零,这会禁用本地帐户过滤。
  • 产品类型是NtProductLanManNt,这实际上对应于域控制器。

如果任一为真,那么只要令牌信息既不是环回也不是强制过滤,函数将返回成功,不发生过滤。因此,在默认安装中,域用户不被过滤取决于机器ID是否匹配。

对于完整性级别,如果发生过滤,则会将其降至KERB-AD-RESTRICTION-ENTRY身份验证数据中的值。但它不会将完整性级别提高到创建令牌默认具有的水平之上,因此不能被滥用以获取系统完整性。

注意:Kerberos会首先使用AP-REQ中票证的KERB-AD-RESTRICTION-ENTRY身份验证数据调用LsaISetSupplementalTokenInfo。如果不存在,则会尝试使用验证器中的条目调用。如果票证和验证器都没有条目,则永远不会被调用。我们如何移除这些值?

好吧,关于那个!

如何滥用此机制绕过UAC?

假设你作为域用户进行身份验证,最有趣的滥用方式是使机器ID检查失败。我们该怎么做?LsapGlobalMachineID值是LSASS启动时生成的随机值。我们可以滥用这样一个事实:如果你查询用户的本地Kerberos票证缓存,即使你不是管理员,它也会返回服务票证的会话密钥(默认情况下不会返回TGT会话密钥)。

因此,一种方法是为本地系统生成服务票证,将生成的KRB-CRED保存到磁盘,重启系统以使LSASS重新初始化,然后在系统恢复后重新加载票证。该票证现在将具有不同的机器ID,因此Kerberos将忽略限制条目。你可以使用内置的klist和Rubeus通过以下命令完成:

1
2
3
4
5
PS> klist get RPC/$env:COMPUTERNAME
PS> Rubeus.exe /dump /server:$env:COMPUTERNAME /nowrap
... 复制base64票证到文件
重启后
PS> Rubeus.exe ptt /ticket:<BASE64 TICKET>

你可以使用Kerberos身份验证通过命名管道或TCP访问SCM,使用RPC/HOSTNAME SPN。注意,SCM的Win32 API总是使用Negotiate身份验证,这会带来问题,但有替代的RPC客户端;-) 虽然LSASS会在AP-REQ的验证器中添加有效的限制条目,但由于票证中的条目会首先被使用,而由于机器ID不同,应用将失败。

另一种方法是生成我们自己的票证,但我们不需要凭据吗?有一个技巧,我相信由Benjamin Delpy发现并放入kekeo,允许你滥用无约束委托获取带有会话密钥的本地TGT。有了这个TGT,你可以生成自己的服务票证,因此你可以执行以下操作:

  1. 使用委托技巧查询用户的TGT。
  2. 使用TGT向KDC请求新的本地机器服务票证。添加KERB-AD-RESTRICTION-ENTRY但填入伪造的机器ID。
  3. 将服务票证导入缓存。
  4. 访问SCM以绕过UAC。

最终,这对于UAC绕过来说是相当多的代码,至少与仅更改环境变量相比。但是,你可能可以使用现有工具(如kekeo和Rubeus)拼凑出来,但我不打算发布交钥匙工具来完成此操作,你得靠自己 :-)

你没忘记KERB-LOCAL吗?

KERB-LOCAL的目的是什么?它是一种重用本地用户凭据的方式,类似于NTLM环回,其中LSASS能够确定调用实际上来自本地身份验证的用户并使用其交互式令牌。票证和验证器中传递的值可以根据Kerberos包中已知凭据列表进行检查,如果匹配,则将使用现有令牌。

这是否不会总是消除基于KERB-AD-RESTRICTION-ENTRY值过滤令牌的需要?由于其设计方式,这种行为似乎很少使用。首先,它仅在接受服务器使用Negotiate包时有效,如果直接使用Kerberos包则无效(有点…)。这通常不是障碍,因为大多数本地服务为方便起见使用Negotiate。

真正的问题是,作为规则,如果你作为客户端使用Negotiate到本地机器,它将选择NTLM作为默认值。这将使用已内置到NTLM中的环回,而不是Kerberos,因此此功能不会被使用。注意,即使NTLM在域网络上全局禁用,它仍将适用于本地环回身份验证。我猜KERB-LOCAL是为了与NTLM的功能对等而添加的。

回到博客开头的格式化票证,KERB-LOCAL值意味着什么?它可以解包为两个64位值,0x17E3303CE60和0x3976FC25。第一个值是LSASS堆中KERB_CREDENTIAL结构的堆地址!!第二个值是创建KERB-LOCAL结构时的票证计数。

幸运的是,LSSAS不会直接解引用凭据指针,它必须在有效凭据结构列表中。但该值未盲化或引用随机生成的值似乎是一个错误,因为堆地址相当容易暴力破解。当然,这并不那么简单,Kerberos确实验证票证PAC中的SID是否与凭据中的SID匹配,因此你不能仅仅欺骗SYSTEM会话,但是,好吧,我将留作思考。

希望这能让你更深入了解此功能的工作原理,以及尝试以新方式绕过UAC的一些乐趣。

更新:这个简单的C++文件可用于修改Win32 SCM API以使用Kerberos进行本地身份验证。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计