文章首发于长亭公众号 CTstack 安全社区 ,原文部分内容有问题,下面是参考了 这篇文章 之后修改过的部分,修改过的部分已经标记处。
Cliff Fisher (Microsoft AD PM) 11.10 在推特上发布了连续发布了几条和AD域有关的CVE漏洞,引发安全人员持续关注。之后Charlie Clark于12.10 在其博客揭秘了CVE-2021-42287的利用方式并给出了武器化利用的手段,于是该漏洞相关消息在国内迅速传播。攻击者仅需要一个域内账户或通过NTLM Relay即可利用此漏洞拿下域控权限。
0x00 Windows域认证基础
域环境下Windows Kerberos认证流程如下:
User与KDC认证,只要账户密码正确即可获得KDC的Ticket(即TGT)与对应的SessionKey,Ticket中扩展了PAC,PAC包括User的用户信息,组信息。
User在拿到KDC的TGT与对应的SessionKey之后向KDC申请Service的Ticket(即TGS),不管User有无访问Service的权限,KDC都会返回给User需要的Ticket(不验证PAC),KDC会把TGT中的PAC数据拷贝到TGS中。
User携带TGS与Service Server建立通信。
Service通过PAC判断User是否有权限访问Service,判断过程:
把PAC传递给KDC判断PAC签名是否正确,确保PAC没有被篡改。
判断User有无权限访问,这部分属于Windows访问控制的内容。
User有权限则Service与User可成功建立通信。
0x01 漏洞复现
在拥有一个域内普通用户且该用户具有创建机器用户的权限下,利用过程如下:
1 | # 0. Create Machine Account |
首先创建机器用户并清除其SPN.
创建机器用户可能会出现自动为其设置SPN的情况,需要清除以方便更改SamAccountName属性
(这里复现的时候新建机器用户并没有自动创建SPN)
然后把机器用户的samaccountname属性修改为DC1
, 修改为DC1
是因为域控机器的名称DC1$
然后使用Rebeus请求所创建机器用户的TGT
之后再修改机器用户的SamAccountName:
然后再根据之前请求的TGT通过S4USelf为Administrator 请求 “自身” LDAP/dc1.test001.com
服务的可转发TGS.
klist可以发现攻击成功.
Windows下可以直接使用https://github.com/cube0x0/noPac利用:
1 | noPac.exe -domain test001.com -user user1 -pass 1q1q1q1Q /dc dc1.test001.com /mAccount de1 /mPassword passworD!123 /service cifs /ptt |
Linux下有https://github.com/WazeHell/sam-the-admin项目可以直接利用
0x02 一些探索
复现完之后存在一些疑惑:
- 为什么是创建机器用户来进行利用?非机器用户是否也可以利用?
于是我创建了一个普通用户来测试:
然后手动修改其SamAccountName
使用demo1请求TGT
修改demo1的SamAccountName之后再请求TGS,发现利用失败
- 为什么获取到DC1的TGT之后是通过S4USelf请求TGS,直接根据TGT请求TGS是否可行?
测试:
1 | Set-MachineAccountAttribute -MachineAccount TestSPN -Value "DC1" -Attribute SamAccountName -Verbose |
发现请求得到的票据是DC1的而不是DC1$的,所以无法利用:
0x03 漏洞分析
根据 https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html 的复现分析.我们可以在泄露的XP源码中找到大概的漏洞代码,下面对几个函数进行分析
KdcGetTicketInfo
KdcGetTicketInfo函数根据GenericUserName从Ticket中获取TicketInfo的大致处理逻辑:
首先判断是否是krbtgt账户,如果是则直接调用GetKrbtgt函数获取TicketInfo
然后再判断是否是本域的用户,并进行了三次查找:
- 首先直接查找传入的用户
- 然后查找 传入的
username+$
- 仍未找到则查找其 altSecurityIdentities 属性的value
由此分析出是KdcGetTicketInfo获取TicketInfo的逻辑存在问题,对UserName做了多余的处理.
但是以上但是以上代码还无法解释为什么获取DC1的TGT之后,通过S4USelf请求DC1$的TGS可以
成功,但是直接根据TGT请求TGS依然是DC1的TGS.
疑惑1
为什么普通用户无法利用,必须使用机器账户才行?
再仔细查找 KdcGetTicketInfo 这部分代码,可以发现根据Username查找是首先根据其Guid从本地SAM文件中查找Username,
普通用户的GUID可以在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileGuid
和HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
找到.
对于SYSTEM用户等,不存在其GUID.
对于机器账户,搜索发现存在注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
可能存在GUID,但是在Server2016和Windows10上并未找到.
所以普通用户无法利用,获取得到SYSTEM等特殊用户其属性值较难更改,需要权限较高,只有使用普通用户创建机器用户再利用的方式比较容易实行.
疑惑2 & KdcInsertAuthorizationData
上面这个问题的原因在于KDC Server对于PAC的处理, S4USelf得到的TGS,其PAC是重新构造的,而直接请求得到的TGS是直接复制的,下面是分析过程:
在KdcInsertAuthorizationData中可以找到KDCServer获取PAC的处理逻辑:
如果不是S4U的请求,则直接从TGT的AuthData中提取PAC
如果是S4U请求,首先调用KdcGetS4UTicketInfo请求获取S4UUserInfo,再调用kdcGetPacAuthData函数来构造PAC data
kdcGetPacAuthData
如果为DoingS4U,则重新生产一个新的PAC。
而下面的逻辑是针对if (!DoingS4U)
的,若未从原票据中成功提权PAC,则会构造一个新的PAC
KdcGetS4UTicketInfo
kdcGetPacAuthData构造PAC信息主要依赖于KdcGetS4UTicketInfo返回的S4UUserInfo结构,S4UUserInfo结构体:
1 | typedef struct _USER_INTERNAL6_INFORMATION { |
其中USER_ALL_INFORMATION是一些关键信息,如UAC,PrimaryGroupId
在KdcGetS4UTicketInfo函数的处理逻辑中发现调用了KdcGetTicketInfo函数
综上解释了为什么该漏洞利用,获取到DC1的TGT之后必须通过S4USelf获取TGS才行.
一些细节
这篇文章的一些细节:
- 为什么要删除SPN?
在这篇文章讲到了如果不删除SPN,在修改samAccountName、DnsHostname或msDS-AdditionalDnsHostName属性时,SPN列表将自动更新其值,所以建议在更改SamAccountName之前删除spn.
- 为什么使用不带PAC的TGS可以请求得到带PAC的ST,这个过程签名如何验证?
TGS_REQ阶段不会校验PAC,所以即使PAC是错误的也不影响获取ST.可以参考微软文档, 只有在AP_REQ请求时,Server会选择是否把PAC传给Server进行验证.
MachineAccountQuota相关内容:
MachineAccountQuota (MAQ)是一个域级别属性默认值为10,即默认情况下允许无特权的用户最多将10台计算机连接到AD域,也就是最多可以新建10个机器用户。且创建者帐户被授予对某些机器帐户对象属性的写访问权限,包括SPN和SamAccountName等关键属性.
samAccountName属性: 可以改变samAccountName的值为任何值, 只要不与别的域账户samAccountName即可. 特殊情况: samAccountName可以用
$
或者空格结尾.
失败的Backdoor利用
根据前面的代码分析以及Charlie Clark文章中的挖掘可以想到域用户的altSecurityIdentities属性也是可以用来做Backdoor的。理论上分析在域管账户的altSecurityIdentities添加一个外域的用户链接即可构成后门,但是却没有复现成功。
首先准备两个域环境,test001.com(内部域)和test.loca(外部域)
首先在test001\admin123账户的altSecurityIdentities属性上添加 Kerberos:hacker123@test.local
值
然后在test.local申请一张不带PAC的hacker123用户的TGT票据:
1 | .\Rubeus_noPac.exe asktgt /user:hacker123 /password:1q1q1q1Q /domain:test.local /dc:exdc1.test.local /nowrap /nopac |
再在test001.com域通过S4U申请一张cifs/dc1.test001.com的TGS,发现会直接报错 KDC_ERR_WRONG_REALM
1 | .\Rubeus_noPac.exe s4u /impersonateuser:Administrator /nowrap /dc:dc1.test001.com /self /altservice:cifs/dc1.test001.com /ptt /ticket:[TGT] |
分析错误原因:与Charlie Clark文章中得到结果不一致的原因可能为Charlie Clark文章中两个域处于同一个根域zeroday.lab中,而我复现时的两个域是不同的根域,所以没有复现成功。
(感兴趣的师傅可以继续尝试)
0x04 检测&&防护
- 安装微软的补丁 KB5008602和KB5008380
- 修改AD域的MachineAccountQuota的属性值为0.
- 检测Windows日志序列,因为攻击会产生很多日志,所以可以通过安全日志检测攻击行为:
- 4673日志:请求SeMachineAccountPrivilege特权
- 4741日志:创建机器用户
- 4724日志:重置新建机器用户的密码
- 4742日志:更改ServicePrincipalNames值为%%1793
- 4742日志:修改TargetUserName为DC1
- 4781日志:已经更改账户名
- 4768日志:请求TGT
- 4742,4781日志,再次修改机器账户名
- 4769日志:请求TGS,存在附加票据信息。