Skip to content
05 · AD Certificate ServicesPart 2 of 4Intermediate

Auditing AD CS certificate templates for ESC1-ESC4

Audit every AD CS certificate template for ESC1-ESC4: subject flags, EKUs, enrollment rights and template ACLs, with PowerShell and a safe fix order.

Florian Amette7 min read

Most AD CS compromises do not need a bug. They need one certificate template that lets a low-privileged user write the subject of their own certificate, or one template ACL that lets that user rewrite the template until it does. The ESC1 to ESC4 classes are all template-level problems, which makes them auditable from LDAP alone: every setting that matters is an attribute on a pKICertificateTemplate object in the configuration partition, and every enrollment right is an ACE on that same object.

The AD CS hardening pillar explains each ESC class conceptually. This guide is the audit procedure behind it: build an inventory of published templates, score each one on the four risk dimensions (subject control, authentication EKUs, who can enroll, who can write), fix them in a safe order, and verify the result. CA-wide settings such as EDITF_ATTRIBUTESUBJECTALTNAME2 and web enrollment are covered separately in securing AD CS web enrollment.

Measure: inventory templates and where they are published

A template only matters for enrollment once a CA publishes it, so the first step is to separate published templates from the 30-odd defaults sitting unused in the directory. Published template names are stored in the certificateTemplates attribute of each CA's pKIEnrollmentService object.

PowerShell
$configNC = (Get-ADRootDSE).configurationNamingContext
$pks      = "CN=Public Key Services,CN=Services,$configNC"
$tplBase  = "CN=Certificate Templates,$pks"
$caBase   = "CN=Enrollment Services,$pks"

# Which CA publishes which template
Get-ADObject -SearchBase $caBase -LDAPFilter '(objectClass=pKIEnrollmentService)' `
    -Properties dNSHostName, certificateTemplates |
    ForEach-Object {
        foreach ($t in $_.certificateTemplates) {
            [pscustomobject]@{ CA = $_.Name; Host = $_.dNSHostName; Template = $t }
        }
    } | Sort-Object Template | Format-Table -AutoSize

Keep this output. It is also your list of enterprise CAs, which belong in your Tier 0 inventory alongside domain controllers.

Audit: score every template on subject, EKU and approval

Four attributes decide whether a template can be used for impersonation:

AttributeWhat to look for
msPKI-Certificate-Name-Flag0x1 (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT): the requester writes the subject and SAN
pKIExtendedKeyUsage / msPKI-Certificate-Application-PolicyClient 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), Any Purpose (2.5.29.37.0), or no EKU at all
msPKI-Enrollment-Flag0x2 (CT_FLAG_PEND_ALL_REQUESTS): CA certificate manager approval required
msPKI-RA-SignatureNumber of authorized signatures (enrollment agent co-signing) required

The script below combines them with the publication data. A template with no EKU is treated as authentication-capable, because a certificate without EKU restrictions is valid for any purpose (the ESC2 case).

PowerShell
$published = Get-ADObject -SearchBase $caBase -LDAPFilter '(objectClass=pKIEnrollmentService)' `
    -Properties certificateTemplates | ForEach-Object { $_.certificateTemplates } | Sort-Object -Unique

$authEku  = '1.3.6.1.5.5.7.3.2','1.3.6.1.4.1.311.20.2.2','1.3.6.1.5.2.3.4','2.5.29.37.0'
$agentEku = '1.3.6.1.4.1.311.20.2.1'   # Certificate Request Agent (ESC3)

Get-ADObject -SearchBase $tplBase -LDAPFilter '(objectClass=pKICertificateTemplate)' -Properties `
    'msPKI-Certificate-Name-Flag','msPKI-Enrollment-Flag','msPKI-RA-Signature',
    'pKIExtendedKeyUsage','msPKI-Certificate-Application-Policy','msPKI-Template-Schema-Version' |
ForEach-Object {
    $eku = @($_.pKIExtendedKeyUsage) + @($_.'msPKI-Certificate-Application-Policy') |
        Where-Object { $_ } | Sort-Object -Unique
    [pscustomobject]@{
        Template        = $_.Name
        Published       = $published -contains $_.Name
        Schema          = $_.'msPKI-Template-Schema-Version'
        SuppliesSubject = [bool]($_.'msPKI-Certificate-Name-Flag' -band 0x1)
        AuthCapable     = ($eku.Count -eq 0) -or [bool]($eku | Where-Object { $_ -in $authEku })
        RequestAgent    = [bool]($eku -contains $agentEku)
        ManagerApproval = [bool]($_.'msPKI-Enrollment-Flag' -band 0x2)
        RASignatures    = [int]$_.'msPKI-RA-Signature'
        EKUs            = $eku -join ', '
    }
} | Where-Object Published | Sort-Object SuppliesSubject, AuthCapable -Descending | Format-Table -AutoSize

Read the output as follows. SuppliesSubject and AuthCapable both true, with no approval and no RA signatures, is a textbook ESC1 candidate: whether it is exploitable depends only on who can enroll. RequestAgent true is an ESC3 candidate. An empty EKUs column on a published template is ESC2.

Two special cases deserve a note. First, schema version 1 templates (such as the built-in WebServer template) that allow a supplied subject were the basis of ESC15 (CVE-2024-49019), where application policies could be injected into the request; the November 2024 security update fixed it on the CA, so confirm your CAs are patched and duplicate v1 templates into v2+ versions you control. Second, templates whose issuance policy is linked to an AD group through msDS-OIDToGroupLink (ESC13) grant that group's membership to whoever holds the certificate, so review any issuance policy OID objects that carry that attribute.

Audit: enrollment rights and template ACLs

Enrollment is an extended right on the template object: Certificate-Enrollment (0e10c968-78fb-11d2-90d4-00c04f79dc55) and Certificate-AutoEnrollment (a05b8cc2-17bc-4802-a710-e7c15ab866a2). GenericAll also grants enrollment. Write rights (WriteProperty, WriteDacl, WriteOwner, GenericWrite, GenericAll) on the template are ESC4: whoever holds them can switch the subject flag on.

PowerShell
$rightNames = @{
    '0e10c968-78fb-11d2-90d4-00c04f79dc55' = 'Enroll'
    'a05b8cc2-17bc-4802-a710-e7c15ab866a2' = 'AutoEnroll'
    '00000000-0000-0000-0000-000000000000' = 'AllExtendedRights'
}
$broad    = '(^|\\)(Everyone|Authenticated Users|Domain Users|Domain Computers|Users)$'
$expected = '(^|\\)(Domain Admins|Enterprise Admins|SYSTEM)$'

Get-ADObject -SearchBase $tplBase -LDAPFilter '(objectClass=pKICertificateTemplate)' |
ForEach-Object {
    $tpl = $_
    (Get-Acl -Path "AD:\$($tpl.DistinguishedName)").Access |
        Where-Object { $_.AccessControlType -eq 'Allow' -and $_.IdentityReference.Value -notmatch $expected } |
        ForEach-Object {
            $r = $_.ActiveDirectoryRights.ToString()
            $kind = if ($r -match 'GenericAll|GenericWrite|WriteDacl|WriteOwner|WriteProperty') { 'WRITE (ESC4)' }
                    elseif ($r -match 'ExtendedRight') { $rightNames[$_.ObjectType.ToString()] }
            if ($kind) {
                [pscustomobject]@{
                    Template  = $tpl.Name
                    Published = $published -contains $tpl.Name
                    Trustee   = $_.IdentityReference.Value
                    Right     = $kind
                    Broad     = $_.IdentityReference.Value -match $broad
                }
            }
        }
} | Sort-Object Published, Right -Descending | Format-Table -AutoSize

Join both outputs. The priority list is any published template that is authentication-capable and either supplies subject or issues request-agent certificates, where Broad is true on an Enroll right. Right behind it is any WRITE (ESC4) row for a non-admin trustee, published or not, since write access to a template is a latent ESC1 waiting for someone to use it. Do not forget the containers themselves: write access on CN=Certificate Templates, CN=Enrollment Services or the NTAuthCertificates object is ESC5 and belongs in the same review, as described in auditing AD ACLs.

Cross-check with Locksmith and PSPKIAudit

Two defender-oriented PowerShell tools are worth running alongside your own scripts. Locksmith (Invoke-Locksmith) reports ESC1-ESC8 and several later classes in its default mode and can generate fix scripts; review its modes before running any that remediate. PSPKIAudit builds on the PSPKI module and adds issued-certificate analysis. Offensive tools such as Certify and Certipy enumerate the same data; if your red team uses them, compare their findings against your inventory rather than treating either as authoritative. For the graph view of who can reach an enrollment right through nested groups, see attack path management with BloodHound.

Enforce: fix in the least disruptive order

Work through findings in this order, which removes the most risk per change while breaking the fewest workflows.

  1. Unpublish templates nobody uses. Check the CA database for recent issuance per template first (see Verify below). Unpublishing is instant and reversible:
PowerShell
# On the CA (ADCSAdministration module)
Get-CATemplate | Sort-Object Name
Remove-CATemplate -Name 'LegacyVPNUser' -Force
  1. Remove broad enrollment rights. Replace Domain Users or Authenticated Users on sensitive templates with a dedicated group that contains only the accounts or computers that need the certificate. Keep Read for Authenticated Users so autoenrollment can still discover templates.
  2. Remove the supplied-subject flag. In the Certificate Templates console (certtmpl.msc), open the template, go to the Subject Name tab and select "Build from this Active Directory information". The console sets the matching subject and SAN flags correctly, which is safer than editing the bitmask directly. Where a supplied subject is a real requirement (web server certificates requested by a provisioning system), remove client authentication EKUs from that template and scope enrollment to the provisioning service.
  3. Add approval or RA signatures on the templates that must keep a supplied subject together with an authentication EKU. On the Issuance Requirements tab, tick "CA certificate manager approval".
  4. Strip write rights from any non-PKI-admin trustee and reset the template owner to Enterprise Admins or your PKI admin group.
  5. Restrict enrollment agents. On the CA's Enrollment Agents tab, restrict each agent to named templates and target groups instead of the default "everyone, all templates".

Changes to template objects replicate through the configuration partition like any other AD change, so make them on one DC and let replication carry them.

Verify

Rerun both audit scripts: no published, authentication-capable template should have both SuppliesSubject and a Broad Enroll right, and no WRITE (ESC4) rows should remain for non-admin trustees.

Confirm who actually uses a template before and after each change, from the CA database:

PowerShell
# Certificates issued from a template in the last 90 days (run on the CA)
$since = (Get-Date).AddDays(-90).ToString('MM/dd/yyyy')
certutil -view -restrict "Disposition=20,NotBefore>=$since" `
    -out "RequestID,RequesterName,CommonName,CertificateTemplate,NotBefore" csv > issued-90d.csv

CertificateTemplate is recorded as the template OID for v2+ templates; map it back with certutil -template output. Then turn on template-change auditing so drift is visible. EDITF_AUDITCERTTEMPLATELOAD makes the CA log event 4898 when it loads a template and 4899 when a template changed, and a SACL on the Certificate Templates container gives you 5136 events with the attribute that changed:

PowerShell
certutil -setreg policy\EditFlags +EDITF_AUDITCERTTEMPLATELOAD
Restart-Service certsvc

Forward 4886, 4887, 4899 and 5136 to your SIEM and alert when a certificate is issued with a SAN that does not match the requester. The auditing and detection pillar covers the collection side.

What it breaks

  • Unpublishing a template: existing certificates stay valid until they expire, but renewal fails. Autoenrolled machines will log enrollment errors in the Application log (source CertificateServicesClient-AutoEnrollment) when they try to renew, so check that log on a sample of clients after each change.
  • Removing Domain Users or Domain Computers enrollment: autoenrollment silently stops for anyone not in the new group. Populate the replacement group before removing the old ACE, not after.
  • Removing the supplied-subject flag: provisioning systems, MDM connectors, load balancer appliances and scripts that submit a CSR with their own subject will get certificates with the AD-derived subject instead, or fail outright. Give them a dedicated template without client authentication EKUs.
  • Manager approval: requests sit pending until a certificate manager acts, which breaks unattended enrollment. Only use it on low-volume templates.
  • Restricting enrollment agents: smart card provisioning staff can only enroll for the templates and groups you list, so new card types need a CA configuration change.

Related reading: the AD Certificate Services topic hub, strong certificate mapping for the KDC side of certificate authentication, and attack path management to find who can reach an enrollment right indirectly.

Frequently asked questions

Does an unpublished certificate template still matter?

Its settings do not matter until a CA publishes it, because nobody can enroll against a template that no CA offers. Its ACL still matters: anyone with write access can modify it, and anyone with Manage CA rights can publish it. Treat unpublished templates as low priority for flag fixes but still clean up write permissions, and delete templates you will never use.

Is manager approval enough to neutralise an ESC1 template?

It turns the template into a gated workflow rather than an open one, which is a large improvement, but it is only as strong as the approvers. A certificate manager who approves requests without reading the requested SAN will still issue a Domain Admin certificate. Prefer removing the enrollee-supplies-subject flag and use approval as a second layer on templates that genuinely need a supplied subject.

Should I run Certify or Certipy against production?

Both are read-only in their enumeration modes, but they are offensive tools and may trigger EDR alerts. For a defensive audit, Locksmith and PSPKIAudit are designed for administrators, and the native PowerShell in this guide covers the same template checks. If you do run an offensive tool, do it from an approved host, with change management aware, and compare results with your own inventory.

Auditing AD CS certificate templates for ESC1-ESC4

Related guides