绕过UAC的最复杂方法:Kerberos协议漏洞利用详解

本文详细分析了如何通过滥用Kerberos协议在域加入系统中绕过UAC保护机制,探讨了KERB-AD-RESTRICTION-ENTRY和KERB-LOCAL认证数据结构的作用,并提供了两种具体的技术实现方法。

绕过UAC的最复杂方法!

虽然我不常花时间研究这个,但找到新的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,你可以生成自己的服务票据,因此你可以执行以下操作:

  • 使用委派技巧查询用户的TGT。
  • 使用TGT向KDC请求本地机器的新服务票据。添加KERB-AD-RESTRICTION-ENTRY但填入伪造的机器ID。
  • 将服务票据导入缓存。
  • 访问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 设计