Certifried: Active Directory Domain Privilege Escalation (CVE-2022–26923)
In this blog post, we’ll dive into a recently patched Active Directory Domain Privilege Escalation vulnerability that I reported through ZDI to Microsoft.
In essence, the vulnerability allowed a low-privileged user to escalate privileges to domain administrator in a default Active Directory environment with the Active Directory Certificate Services (AD CS) server role installed. At Institute For Cyber Risk, we see AD CS environments on almost every engagement. It’s rare that we see large and medium-sized Active Directory environments without AD CS installed. The vulnerability was patched as part of the May 2022 Security Updates from Microsoft.
Background
In Summer 2021, Will Schroeder and Lee Christensen published their excellent whitepaper Certified Pre-Owned: Abusing Active Directory Certificate Services which took a deep dive into the security of Active Directory Certificate Services (AD CS). The whitepaper thoroughly explained various tricks for persistence, theft, and privilege escalation — but also defensive guidance and general documentation on AD CS.
When I initially read the whitepaper from Will Schroeder and Lee Christensen, I only began researching into abusing misconfigurations. It was not until December 2021 when I got inspired by Charlie Clark’s (@exploitph) blog post on CVE-2021–42287 and CVE-2021–42278 that I started to look into actual vulnerabilities related to AD CS.
Introduction to Active Directory Certificate Services
If you already feel comfortable with the basics of Active Directory Certificate Services, you can skip this section. On the other hand, if you’re still feeling a bit perplexed about public key infrastructure (PKI) and certificates after reading this section, don’t worry. For this vulnerability, you can think of a certificate as merely a prove of identification, similar to a Kerberos ticket.
If you haven’t already, I highly recommend reading the shortened version of “Certified Pre-Owned” before continuing. I’ll try to cover some details throughout this post as well, but Will Schroeder and Lee Christensen has already done a great job at explaining the essentials, so here’s a snippet from their blog post that perfectly summarizes AD CS.
AD CS is a server role that functions as Microsoft’s public key infrastructure PKI implementation. As expected, it integrates tightly with Active Directory and enables the issuing of certificates, which are X.509-formatted digitally signed electronic documents that can be used for encryption, message signing, and/or authentication.
The information included in a certificate binds an identity (the subject) to a public/private key pair. An application can then use the key pair in operations as proof of the identity of the user. Certificate Authorities (CAs) are responsible for issuing certificates.
At a high level, clients generate a public-private key pair, and the public key is placed in a certificate signing request (CSR) message along with other details such as the subject of the certificate and the certificate template name. Clients then send the CSR to the Enterprise CA server. The CA server then checks if the client is allowed to request certificates. If so, it determines if it will issue a certificate by looking up the certificate template AD object […] specified in the CSR. The CA will check if the certificate template AD object’s permissions allow the authenticating account to obtain a certificate. If so, the CA generates a certificate using the “blueprint” settings defined by the certificate template (e.g., EKUs, cryptography settings, issuance requirements, etc.) and using the other information supplied in the CSR if allowed by the certificate’s template settings. The CA signs the certificate using its private key and then returns it to the client.
That’s a lot of text. So here’s a graphic:
In essence, users can request a certificate based on a predefined certificate template. These templates specifies the settings for the final certificate, e.g. whether it can be used for client authentication, what properties must be defined, who is allowed to enroll, and so on. While AD CS can be used for many different purposes, we will only focus on the client authentication aspect of AD CS.
So, let’s just make a quick example on how certificates can be used for authentication in Active Directory. We’ll be using Certipy to request and authenticate with the certificate. I have created the domain CORP.LOCAL
with AD CS installed. I have also created a default, low-privileged user named JOHN
. In the example below, we request a certificate from the CA CORP-DC-CA
based on the template User
. We then use the issued certificate john.pfx
for authentication against the KDC. When authenticating with a certificate, Certipy will try to request a Kerberos TGT and retrieve the NT hash of the account.
Vulnerability
Discovery
By default, domain users can enroll in the User
certificate template, and domain computers can enroll in the Machine
certificate template. Both certificate templates allow for client authentication. This means that the issued certificate can be used for authentication against the KDC via the PKINIT Kerberos extension.
So why does AD CS have different templates for users and computers, one might ask? In short, user accounts have a User Principal Name (UPN), whereas computer accounts do not. When we request a certificate based on the User
template, the UPN of the user account will be embedded in to the certificate for identification. When we use the certificate for authentication, the KDC tries to map the UPN from the certificate to a user. If we look at the msPKI-Certificate-Name-Flag property of the User
template, we can also see that SubjectAltRequireUpn
(CT_FLAG_SUBJECT_ALT_REQUIRE_UPN
) is specified.
As per MS-ADTS (3.1.1.5.1.3 Uniqueness Constraints), the UPN must be unique, which means we cannot have two users with the same UPN. For instance, if we try to change the UPN of Jane
to John@corp.local
, we will get a constraint violation, since the UPN John@corp.local
is already used by John
.
As mentioned previously, computer accounts do not have a UPN. So what do computer accounts then use for authentication with a certificate? If we look at the Machine
certificate template, we see that SubjectAltRequireDns
(CT_FLAG_SUBJECT_ALT_REQUIRE_DNS
) is specified instead.
So let’s try to create a new machine account, request a certificate, and then authenticate with the certificate.
As we can see above, the certificate is issued with the DNS host name JOHNPC.corp.local
, and if we look at the computer account JOHNPC$
, we can notice that this value is defined in the dNSHostName
property.
If we look at the permissions of the JOHNPC
object, we can see that John
(the creator of the machine account) has the “Validated write to DNS host name” permission.
The “Validated write to DNS host name” permission is explained here, and described as “Validated write permission to enable setting of a DNS host name attribute that is compliant with the computer name and domain name.” So what does “compliant with the computer name and domain name” mean?
If we (as John
) try to update the DNS host name property of JOHNPC
to TEST.corp.local
, we encounter no issues or constraint violations, and the SAM Account Name of JOHNPC
is still JOHNPC$
.
So let’s try to request a certificate now.
We notice that the certificate is now issued with the DNS host name TEST.corp.local
. So now we are fairly certain that the DNS host name in the issued certificate is derived from the dNSHostName
property, and John
(as the creator of the machine account) has the “Validated write to DNS host name” permission.
Vulnerability
If we read the MS-ADTS (3.1.1.5.1.3 Uniqueness Constraints) documentation, nowhere does it mention that the dNSHostName
property of a computer account must be unique.
If we look at the domain controller’s (DC$
) dNSHostName
property, we find that the value is DC.CORP.LOCAL
.
So without further ado, let’s try to change the dNSHostName
property of JOHNPC
to DC.CORP.LOCAL
.
This time, we get an error message saying “An operations error occurred”. This is different than when we tried to change the UPN to another user’s UPN, where we got a constraint violation. So what really happened?
Well, if we looked carefully when we changed the dNSHostName
property value of JOHNPC
from JOHNPC.corp.local
to TEST.corp.local
, we might have noticed that the servicePrincipalName
property value of JOHNPC
was updated to reflect the new dNSHostName
value.
And according to MS-ADTS (3.1.1.5.1.3 Uniqueness Constraints), the servicePrincipalName
property is checked for uniqueness. So when we tried to update the dNSHostName
property of JOHNPC
to DC.corp.local
, the domain controller tried to update the servicePrincipalName
property, which would be updated to include RestrictedKrbHost/DC.corp.local
and HOST/DC.corp.local
, which would then conflict with the domain controller’s servicePrincipalName
property.
So by updating the dNSHostName
property of JOHNPC
, we indirectly caused a constraint violation when the domain controller also tried to update the servicePrincipalName
of JOHNPC
.
If we take a look at the permissions of JOHNPC
, we can also see that John
(as the creator of the machine account) has the “Validated write to service principal name” permission.
The “Validated write to service principal name” permission is explained here, and described as “Validated write permission to enable setting of the SPN attribute which is compliant to the DNS host name of the computer.” So if we want to update the servicePrincipalName
of JOHNPC
, the updated values must also be compliant with the dNSHostName
property.
Again, what does “compliant” mean here? We notice that only two values are updated and checked when we update the dNSHostName
, namely RestrictedKrbHost/TEST.corp.local
and HOST/TEST.corp.local
, which contains the dNSHostName
property value. The other two values RestrictedKrbHost/JOHNPC
and HOST/JOHNPC
contains the sAMAccountName
property value (without the trailing $
).
So only the servicePrincipalName
property values that contain the dNSHostName
value must be compliant with dNSHostName
property. But can we then just delete the servicePrincipalName
values that contain the dNSHostName
?
Yes we can. So if we now try to update the dNSHostName
property value of JOHNPC
to DC.corp.local
, the domain controller will not have to update the servicePrincipalName
, since none of the values contain the dNSHostName
property value.
Let’s try to update the dNSHostName
property value of JOHNPC
to DC.corp.local
.
Success! We can see that the dNSHostName
property was updated to DC.corp.local
, and the servicePrincipalName
was not affected by the change, which means we didn’t cause any constraint violations.
So now JOHNPC
has the same dNSHostName
as the domain controller DC$
.
Now, let’s try to request a certificate for JOHNPC
using the Machine
template, which should embed the dNSHostName
property as identification.
Another success! We got a certificate with the DNS host name DC.corp.local
. Let’s try to authenticate using the certificate.
Authentication was also successful, and Certipy retrieved the NT hash for dc$
. As a Proof-of-Concept, we can use the NT hash to perform a DCSync attack to dump the hashes of all the users.
You might have wondered, why we didn’t have to change the DNS host name of JOHNPC
to something else before authenticating with the certificate. How did the KDC know what account to map the certificate to?
PKINIT & Certificate Mapping
If you don’t care about the technical details on how certificates are mapped to accounts during authentication, you can skip this section.
Public Key Cryptography for Initial Authentication (PKINIT) is an extension for the Kerberos protocol. The PKINIT extension enables the use of public key cryptography in the initial authentication exchange of the Kerberos protocol. In other words, PKINIT is the Kerberos extension that allows the use of certificates for authentication. In order to use a certificate for Kerberos authentication, the certificate must be configured with the “Client Authentication” Extended Key Usage (EKU), and some sort of identification of the account. The Windows implementation of the PKINIT protocol extension for Kerberos is described in MS-PKCA. The documentation specifies, among other things, how the KDC maps a certificate to an account during authentication. The certificate mapping is explained in MS-PKCA 3.1.5.2.1.
First, the account is looked up based on the principal name specified in the AS-REQ, e.g. user@corp.local
. Then, depending on the userAccountControl
property of the account, the KDC validates the certificate mapping based on either the Subject Alternative Name (SAN) DNSName or UPNName in the certificate. If the WORKSTATION_TRUST_ACCOUNT
(domain computer) or SERVER_TRUST_ACCOUNT
(domain controller) bit is set, the KDC validates the mapping from the DNSName. Otherwise, the KDC validates the mapping from the UPNName. For this blog post, we’re only interested in the DNSName mapping. The mapping of the DNSName field is described in MS-PKCA 3.1.5.2.1.1.
The documentation states that the KDC must confirm that the sAMAccountName
of the account looked up 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. As an example, suppose we have the computer account JOHNPC$
in the domain corp.local
. For a valid mapping, the DNSName of the certificate must therefore be JOHNPC.corp.local
, i.e. <computername>.<domain>
, where <computername>
is the sAMAccountName
without the trailing $
.
So during PKINIT Kerberos authentication, we supply a principal name (e.g. johnpc$@corp.local
) and a certificate with a DNSName set to johnpc.corp.local
. The KDC then looks up the account from the principal name. Since johnpc$
is a computer account, the KDC then splits the DNSName field into a computer name and realm part. The KDC then validates that the computer name part matches the sAMAccountName
terminated with $
and that the realm part matches the domain. If both parts match, the validation is a success, and the mapping is thus valid. It is worth noting that the dNSHostName
property of the account is not used for the certificate mapping. The dNSHostName
property is only used when the certificate is requested.
Patch
UPDATED MAY 11
The vulnerability was patched as part of the May 2022 Security Updates from Microsoft by introducing a new Object ID (OID) in new certificates to further fingerprint the user. This is done by embedding the user’s objectSid
(SID) within the new szOID_NTDS_CA_SECURITY_EXT
(1.3.6.1.4.1.311.25.2
) OID. Certificate Templates with the new CT_FLAG_NO_SECURITY_EXTENSION
(0x80000
) flag set in the msPKI-Enrollment-Flag
attribute will not embed the new szOID_NTDS_CA_SECURITY_EXT
OID, and therefore, these templates are still vulnerable to this attack. It is unlikely that this flag is set, but you should be aware of the implications of turning this flag on. Furthermore, the “Validated write to DNS host name” permission now only allows setting a dNSHostName
attribute that matches the SAM Account Name of the account. However, with a generic write permission over the computer account, it’s still possible to create a duplicate dNSHostName
value.
An attempt to exploit the vulnerability against a patched domain controller will return KDC_ERR_CERTIFICATE_MISMATCH
during Kerberos authentication, if the certificate has the szOID_NTDS_CA_SECURITY_EXT
OID. I also tried to perform the authentication using Schannel against LDAPS to check whether the vulnerability was only patched in the Kerberos implementation. Fortunately, it seems that this method can’t bypass the security update. There might be some other interesting cases, since the dNSHostName
property can still be duplicated and embedded in the certificate. To check if a CA is vulnerable, we can simply request a certificate and check whether the user’s SID is embedded within the certificate. It is worth noting that both the KDC and CA server must be patched in order to fully mitigate the vulnerability.
This patch also brings an end to the ESC6 attack described in Will Schroeder and Lee Christensen’s whitepaper; but the ESC1 attack will still work, since the new OID isn’t embedded in certificates based on certificate templates with the ENROLLEE_SUPPLIES_SUBJECT
flag specified.
Certipy
Along with release of this blog post, Certipy has received some new updates that includes functionality to easily create a new machine account with the DNS host name dc.corp.local
and then request a certificate.
Mitigations
A patch has officially been released by Microsoft. If you’re unable to install the patch, there are a few other measures you can take to mitigate the vulnerability. First of all, you can harden your AD CS environment by restricting certificate enrollment. While not directly a mitigation, you can also change the MS-DS-Machine-Account-Quota
attribute to 0
, which is the value that determines the number of computer accounts that a user is allowed to create in a domain. By default, this value is set to 10
. This does not mitigate the vulnerability, since an attacker might compromise a machine account by compromising a workstation, for instance with KrbRelay.
Disclosure Timeline
- Dec 14, 2021: Vulnerability reported to Zero Day Initiative
- Dec 17, 2021: Case assigned
- Dec 31, 2021: Case investigated
- Jan 11, 2022: Case contracted
- Jan 20, 2022: Case reviewed
- Jan 21, 2022: Vendor disclosure, tracked as ZDI-CAN-16168
- May 10, 2022: Patch released by Microsoft
.