AD-ADCS-006: ESC9: authentication template without the security extension¶
medium Certificate services MITRE ATT&CK T1649 Reads: LDAP
A published authentication template that broad groups can enroll in sets CT_FLAG_NO_SECURITY_EXTENSION, so its certificates carry no SID for strong mapping.
Also known as: esc9
Why it matters¶
Since the May 2022 updates (KB5014754), a CA writes the requester's SID into every
certificate (szOID_NTDS_CA_SECURITY_EXTENSION), and domain controllers use it to tie
the certificate to exactly one account. The template flag CT_FLAG_NO_SECURITY_EXTENSION
tells the CA to leave that SID out, so the DC falls back to mapping by the UPN in the
certificate.
The ESC9 attack needs write access to one other account, for example GenericWrite over
jdoe:
- Set
jdoe'suserPrincipalNametoadministrator(no domain suffix). - Enroll as
jdoein this template; the certificate's UPN is nowadministrator, and it carries no SID. - Set
jdoe's UPN back, so the name no longer matchesjdoe. - Authenticate with the certificate: a DC in compatibility mode maps it to
administrator.
Domain controllers in full enforcement (StrongCertificateBindingEnforcement = 2, the
default since the February 2025 updates) reject a certificate without the SID extension
unless the account has a strong explicit mapping, which blocks this path for Kerberos.
How ad-doctor detects it¶
ad-doctor reads every certificate template and the enterprise CAs over LDAP and reports a template when all of these hold:
| Condition | Attribute |
|---|---|
| Published on at least one enterprise CA | certificateTemplates of a CA object |
| No manager approval | msPKI-Enrollment-Flag without CT_FLAG_PEND_ALL_REQUESTS (0x2) |
| No authorized signatures | msPKI-RA-Signature is 0 |
| A broad group can enroll | Enroll or AutoEnroll in the template DACL for Everyone, Anonymous Logon, Authenticated Users, BUILTIN\Users, Pre-Windows 2000 Compatible Access, Domain Users, Domain Guests or Domain Computers |
| No SID extension | msPKI-Enrollment-Flag has CT_FLAG_NO_SECURITY_EXTENSION (0x80000) |
| Usable for authentication | EKU includes Client Authentication, Smart Card Logon, PKINIT Client Authentication or Any Purpose, or is empty |
Each object names the template and the broad groups that can enroll, for example
LabESC9 (enroll: Domain Users). The same effect configured CA-wide is reported by
AD-ADCS-015.
Attributes and probes read
msPKI-Enrollment-Flag (CT_FLAG_NO_SECURITY_EXTENSION 0x80000)pKIExtendedKeyUsagemsPKI-Certificate-Application-PolicymsPKI-RA-SignaturenTSecurityDescriptor (template DACL)certificateTemplates (on pKIEnrollmentService)
Severity¶
Always medium: the template is only exploitable together with write access to another
account's userPrincipalName, and only against domain controllers that are not in full
enforcement mode for strong certificate mapping.
False positives and limits¶
- Full enforcement on every DC. ad-doctor does not read the DCs'
StrongCertificateBindingEnforcementvalue, so it cannot tell whether all of them are in full enforcement mode. The finding still matters for Schannel logons that map certificates by UPN, and the flag has no legitimate use on an authentication template. - Nobody can write other accounts' UPNs. The attack needs that write access; who holds it over privileged accounts is reported by AD-ACL-004 and AD-PATH-001.
Fix¶
-
Clear the flag on the template (
CT_FLAG_NO_SECURITY_EXTENSION, 0x80000):$cfg = (Get-ADRootDSE).configurationNamingContext $dn = "CN=LabESC9,CN=Certificate Templates,CN=Public Key Services,CN=Services,$cfg" $t = Get-ADObject $dn -Properties 'msPKI-Enrollment-Flag' Set-ADObject $dn -Replace @{'msPKI-Enrollment-Flag' = ($t.'msPKI-Enrollment-Flag' -band -bnot 0x80000)} -WhatIf -
Confirm full enforcement on every DC (read-only; the value should be 2 or absent on a fully updated DC):
-
Re-enroll holders of certificates from this template so their certificates carry the SID, and remove write access to UPNs that non-admins should not have.
Verify¶
To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):
[[accept]]
check = "AD-ADCS-006"
objects = ["..."] # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31
Related checks¶
References¶
Microsoft and vendors
Specifications
Tools
MITRE ATT&CK
Available since 0.1.0.