ESC1: when your certificate server mints domain admin on request
A certificate template that lets any domain user write their own name on the request is a domain admin factory. Build subjects from AD, scope who can enrol, and keep NDES from becoming your Tier 0.
01 The Big PictureFor leadership · no jargon
Okay, so this one holds a personal record for me: five minutes from plugging into a client's network to full domain admin, and no exploit was involved. I asked their certificate server for a certificate that said I was the administrator, and it printed one. This guide walks through ESC1, the AD CS misconfiguration behind that, how to find every template that allows it, and how to close it without breaking your Wi-Fi. Let's get stuck in!
Most Active Directory estates run a certificate authority, the internal passport office that issues certificates saying "this person or machine is who they claim to be, we checked". Most of the templates it issues from are sensible: the office fills in your name from the staff directory itself, so you get a passport with your name on it. ESC1 is what happens when a template instead lets the applicant write in their own name. Any ordinary employee account can then apply for a passport that says "CEO", or "Domain Admin", and the passport office stamps it without blinking. The name SpecterOps gave the whole family of these attacks is Certified Pre-Owned, and ESC1 is the first and most famous of the lot.
Because certificates are accepted as proof of identity, that stamped passport logs you in as whoever it names. This is how a pentester converts "we have one phished login" into "we need to talk about your domain controllers" before the kick-off coffee has gone cold. The fix is not a patch, because nothing is broken: one checkbox per template (let the office fill in the name itself), plus tightening who is allowed to apply. Minutes of work, no downtime. The only real trade-off is that some legitimate systems, device enrolment mostly, genuinely need applicants to write their own names, and those need a different treatment, which the community has strong opinions about and we'll get to. And if you're reading this because an audit just lit up your templates: don't panic, you're in good company, and everything below is an afternoon's work :)
02 Technical BreakdownFor engineers
Right, let's get into the nitty-gritty. ESC1 is a property of a certificate template, and it needs four things to line up at once. First, the template's Subject Name setting is "Supply in the request" (the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT bit, 0x1, in the template's msPKI-Certificate-Name-Flag attribute), which lets the requester put an arbitrary Subject Alternative Name into the CSR. Second, the template's EKU allows client authentication (OID 1.3.6.1.5.5.7.3.2, or Smart Card Logon, or Any Purpose, or no EKU at all), so the issued certificate can be used to log in. Third, a low-privileged group like Domain Users holds Enroll rights on it. Fourth, issuance needs no manager approval and no authorised signatures, so the CA stamps the request immediately. All four at once, and any user can enrol a certificate for any UPN they fancy.
The exploitation is embarrassingly short (trust me, I've timed it):
certipy find -u [email protected] -p 'Passw0rd' -dc-ip 10.0.0.10 -vulnerable -stdout
certipy req -u [email protected] -p 'Passw0rd' -ca corp-DC-CA -target ca.corp.local -template VulnTemplate -upn [email protected]
certipy auth -pfx administrator.pfx -dc-ip 10.0.0.10 Find vulnerable templates, request one as administrator, authenticate. That last command hands you a Kerberos TGT *and* the account's NT hash. On Windows the same job is Certify.exe find /vulnerable then Certify.exe request /ca:... /template:... /altname:[email protected], traded for a TGT via Rubeus.
Two things make this spicier than it first looks. One, the certificate is independent credential material: resetting the impersonated admin's password does nothing to it, and it keeps working until it expires (template validity is often a year or more). Two, and this is the bit everyone gets wrong, patching does not fix ESC1. The May 2022 updates (KB5014754, the CVE-2022-26923 "Certifried" family) hardened certificate *mapping* on domain controllers and made CAs stamp the account's SID into new certificates. But for a supply-in-the-request template, the CA resolves the name you supplied and stamps *that* account's SID, so an ESC1 certificate sails through strong certificate mapping, even in full enforcement mode. Patching closes Certifried; only configuration closes ESC1. Fully patched domains are exactly as open here as unpatched ones.
03 Affected
Any AD CS enterprise CA publishing a template with all four conditions above, on any Windows Server version, patched or otherwise. In the wild these templates mostly appear in two ways. Someone duplicates a built-in template like User or Web Server and ticks "Supply in the request" to make some application happy, inheriting the default Domain Users Enroll right in the process. Or a vendor's own setup guide tells you to: one researcher catalogued ten vendor guides (Citrix, Workspace ONE, Kandji, Cisco and friends) whose official instructions create an ESC1-vulnerable template, which is the sort of thing that makes you want a lie down.
The built-in Web Server template ships with "Supply in the request" enabled, but only carries the Server Authentication EKU, so on its own it can't mint logon certificates; it's the moment someone duplicates it or adds Client Authentication that it becomes ESC1. Also check the CA itself: if EDITF_ATTRIBUTESUBJECTALTNAME2 is set in the CA's EditFlags (that's ESC6), every client-auth template on that CA behaves like supply-in-the-request regardless of its own settings. Existing issued certificates are unaffected by the template fix and stay valid until expiry or revocation, so the retrospective hunt matters as much as the flag flip.
04 Detection
Before we change anything, let's see what we're dealing with. First, the attacker's-eye view, the exact check a pentester runs against you:
certipy find -u <user>@<domain> -p <pass> -dc-ip <DC_IP> -vulnerable -stdout Any ordinary user's creds will do; that's the point. Certify.exe find /vulnerable from a domain-joined Windows box does the same job.
From the defensive side, SpecterOps' PSPKIAudit does a proper forest audit:
Invoke-PKIAuditFlags ESC1 through ESC8 per template and CA. The ESC1 rows are the ones with EnrolleeSuppliesSubject, an authentication EKU, low-priv enrolment and no manager approval all at once. Locksmith (Trimarc) is the other community favourite and emits ready-made remediation snippets per finding.
To eyeball a specific template by hand:
Get-ADObject -SearchBase "CN=Certificate Templates,CN=Public Key Services,CN=Services,$((Get-ADRootDSE).configurationNamingContext)" -Filter {Name -eq 'WebServer'} -Properties msPKI-Certificate-Name-Flag, msPKI-Enrollment-Flag A msPKI-Certificate-Name-Flag with the 0x1 bit set means supply-in-the-request. 0x2 set in msPKI-Enrollment-Flag means manager approval is on. Or just open certtmpl.msc and read the Subject Name tab, no shame in that.
Now the retrospective hunt: has anyone already done this? CA auditing is off by default (of course it is), so turn it on first:
certutil -setreg CA\AuditFilter 127
Restart-Service certsvcEnable every audit category, then pair it with Advanced Audit Policy, Object Access, Audit Certification Services. From then on the CA's Security log records event 4886 (request received) and 4887 (certificate issued): watch those for SANs naming privileged accounts requested by unprivileged users.
And for what's already in the wild:
Get-CertificationAuthority | Get-CertRequest -HasSANPSPKIAudit's Get-CertRequest pulls issued certificates with SANs from the CA database. A SAN naming a Domain Admin, requested by someone who isn't one, is your smoking gun; revoke it with Revoke-Certificate and start asking questions.
05 Remediation
01 Build the subject from Active Directory
The canonical fix, per template: open certtmpl.msc, template properties, Subject Name tab, and switch from "Supply in the request" to "Build from this Active Directory information". Effective immediately, no restart, and existing certificates keep working until expiry. With the flag gone the CA ignores whatever subject the CSR asks for and stamps the enrollee's own directory identity, which kills the entire attack class for that template in one click.
The checkbox is the fix. Everything else in this section is for templates where you genuinely can't untick it.
Worth a sanity check first: look at the CA's issued-certificates list for that template. If nothing has ever enrolled from it, you almost certainly don't need it published at all, and the r/sysadmin consensus is emphatic here: unpublishing a template nothing uses is the safest move in this whole guide (certsrv.msc, Certificate Templates, delete; the AD object stays, issuance stops).
02 Scope who can enrol
On the template's Security tab, remove Enroll and Autoenroll from Domain Users, Authenticated Users and Domain Computers, and grant it to a dedicated group containing only the accounts that legitimately enrol. Two practitioner notes from the trenches: prefer Domain Users over Authenticated Users as your floor (the latter includes guests and trusted-forest users), and after adding a machine to the enrolment group, reboot it or purge its ticket cache (klist -li 0x3e7 purge) or enrolment will fail mysteriously and you'll blame the wrong thing. While you're in there, remember that manager approval can be bypassed by anyone holding Manage CA rights on the CA itself, so the CA's own ACL deserves the same scrutiny.
03 When supply-in-request genuinely has to stay
Here's the honest bit, and where the Reddit war stories live. Yeah, I get it: "just untick the box" is easy to say when you're not the one whose device enrolment depends on it. But hear me out, because there are options short of leaving the door open, and the community has settled on a good few of them. The problem is NDES/SCEP device enrolment (Intune, Jamf, other MDMs, network devices, printers): it builds the subject on the device side, so Microsoft's own Intune SCEP docs *require* a supply-in-the-request template, and flipping it to build-from-AD breaks those enrolments overnight. The community answer is never "delete it", it's containment: scope Enroll to the dedicated service account only and treat that account and its NDES host as Tier 0, add "CA certificate manager approval" on the Issuance Requirements tab for anything enrolled manually (a certificate manager then approves each request in certsrv.msc), or require authorised signatures so CSRs must be co-signed by an enrolment agent. Fancier options people report success with: a policy module like the open-source TameMyCerts that validates requested names server-side, or a dedicated issuing CA for device certs kept out of the NTAuth store if those certs never authenticate to AD. One more real-world pattern from r/sysadmin for long-lived manually-renewed certs: flip the template to supply-in-request, enrol the cert, flip it straight back.
Do note the interaction the other way: manager approval is a fine compensating control for manual enrolment but it silently breaks GPO auto-enrolment and NDES, because pending requests are, well, pending. And removing the Client Authentication EKU from a template kills anything using those certs to log on (smart cards, 802.1x, VPN), so check the issued-certs list before you trim EKUs.
04 Check the CA itself (the ESC6 sweep)
If EDITF_ATTRIBUTESUBJECTALTNAME2 is set at the CA level, every template on it is supply-in-request regardless of its Subject Name tab, so check before you declare victory:
certutil -config "CA_HOST\CA_NAME" -getreg policy\EditFlags If EDITF_ATTRIBUTESUBJECTALTNAME2 appears in the output, remove it with certutil -config "CA_HOST\CA_NAME" -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 and restart certsvc. Microsoft removed it from defaults years ago precisely because of this.
06 Verification
Fixed? Let's prove it rather than assume it. Re-run the attacker's check:
certipy find -u <user>@<domain> -p <pass> -dc-ip <DC_IP> -vulnerable -stdoutThe template should no longer appear as ESC1. This is the exact output your next pentest report will quote, so get it clean now.
And the end-to-end test, as a bog-standard user:
certipy req -u [email protected] -p 'Passw0rd' -ca corp-DC-CA -target ca.corp.local -template FixedTemplate -upn [email protected]The request should either be denied outright or, on a build-from-AD template, succeed but issue a certificate naming *john*, which is the desired punchline: the CA ignored your requested UPN. Inspect the issued cert's SAN to confirm.
Finally, watch the forward-looking signals for a few weeks: event 4887 on the CA for SAN/requester mismatches, and KDC events 39, 40 and 41 on your DCs for weak certificate mappings while you're in the neighbourhood. PingCastle, Purple Knight or Defender for Identity's certificate posture assessments will re-flag the domain if anything regresses, which conveniently gives you an external re-check.
07 Rollback
The realistic rollback is discovering that a device-enrolment flow was leaning on supply-in-request: SCEP enrolments start failing, NDES logs denials, and the Wi-Fi team appears at your desk. Restore the flag per template in seconds: certtmpl.msc, Subject Name tab, back to "Supply in the request". And look, this isn't "losing" to the misconfiguration: a template that goes back with scoped enrolment and manager approval is a very different beast to the free-for-all you started with.
Roll back per template, never forest-wide, and treat the rollback as a TODO with an owner, not a destination: the template goes back on the "containment needed" list from the previous section (scoped enrolment, manager approval, Tier 0 service account).
Enrolment rights are rolled back by re-adding the original group to the template's Security tab. If a revoked certificate turns out to have been legitimate (it happens), you can't un-revoke, so re-issue instead. And if the thing that "broke" is actually KB5014754 strong-mapping enforcement on your DCs rather than your template change (Event 39s on the DCs are the tell), that's a different workstream: reissue the affected certs from a patched CA so they carry the SID extension, or map them explicitly via altSecurityIdentities. Don't roll back the domain's certificate hardening to make one template quiet.
08 References
- SpecterOps: Certified Pre-Owned (whitepaper)
- SpecterOps: Certified Pre-Owned, Abusing Active Directory Certificate Services
- Microsoft KB5014754: Certificate-based authentication changes on Windows domain controllers
- GhostPack: PSPKIAudit
- ly4k: Certipy
- Trimarc: Locksmith
- privdebug: From Vendor to ESC1 (ten vendor guides that create the hole)
- r/sysadmin: AD CS templates requiring custom Subject Name without introducing vulnerabilities
- r/activedirectory: CA template ESC1 vulnerability (Subordinate Certification Authority)
- projectblack: Fixing ESC1
Fingers crossed your template hunt comes back clean! If it doesn't, you're in good company, and you're already ahead of every estate that never looks :) If I've got anything wrong here, or you've hit an edge case I haven't covered, do say: corrections and questions are always welcome :)
Ship the fix. Prove it’s closed.
If this runbook doesn’t fit your stack, send the CVE — an operator writes a verified path, usually within a business day. Or contribute your own and get co-authored credit.