adcs-attack-methodology.md (15616B)
1 --- 2 title: "ADCS Attack Methodology" 3 description: "ADCS/ESC attack index following Certified Pre-Owned taxonomy: ESC, THEFT, PERSIST, DPERSIST phases." 4 category: active-directory 5 subcategory: "ADCS & Certificates" 6 tags: [active-directory, adcs, esc, certificates] 7 tools: [Certipy, Certify] 8 difficulty: advanced 9 updated: "2026-08-09" 10 source: "vault:ActiveDirectory/ACL-ESC-Techniques/_ADCS Attack Methodology Guide.md" 11 --- 12 13 # ADCS Attack Methodology 14 15 > **Purpose —** How an ADCS engagement flows from enumeration through escalation, credential theft, and domain persistence. Follows the SpecterOps **Certified Pre-Owned** taxonomy: **ESC** (escalation), **THEFT** (credential theft), **PERSIST** (account persistence), **DPERSIST** (domain persistence). 16 17 ## 1. The ADCS Attack Surface 18 19 Active Directory Certificate Services binds a cryptographic identity (a certificate) to an AD principal. The moment a certificate can carry an *authentication* EKU and an attacker can influence *whose* identity is stamped into it, the certificate becomes a password-equivalent that survives password resets. Four things go wrong: 20 21 <figure class="flow plate corners"> 22 <figcaption class="flow__cap"><span class="flow__kind">ADCS attack surface</span><span class="flow__dir">TD</span></figcaption> 23 <div class="flow__body"> 24 <div class="flow__diagram" data-dir="td"> 25 <div class="flow-rank"> 26 <div class="flow-node">Template misconfig<span class="sub">ESC1-3, ESC9, ESC15</span></div> 27 <div class="flow-node">Object/CA ACL abuse<span class="sub">ESC4, ESC5, ESC7</span></div> 28 <div class="flow-node">CA/DC config or bug<span class="sub">ESC6, ESC8, ESC10-16, Certifried</span></div> 29 <div class="flow-node">Existing cert theft<span class="sub">THEFT1-5</span></div> 30 </div> 31 <div class="flow-join"></div> 32 <div class="flow-rank"><div class="flow-node is-goal">Attacker obtains a cert<span class="sub">for a privileged identity</span></div></div> 33 <div class="flow-edge"></div> 34 <div class="flow-rank"><div class="flow-node">PKINIT auth<span class="sub">TGT + NT hash</span></div></div> 35 <div class="flow-edge"></div> 36 <div class="flow-rank"><div class="flow-node is-danger">Persistence<span class="sub">PERSIST1-3, DPERSIST1-3</span></div></div> 37 </div> 38 </div> 39 </figure> 40 41 > **Why certificates are dangerous —** A stolen or forged authentication certificate is valid until it **expires** (often 1–5 years) or is **revoked**. Password changes do not invalidate it. Forged certs (Golden Certificate, rogue CA) are never seen by the CA's issuance pipeline, so they **cannot be revoked**. 42 43 ## 2. Phase 1 — Enumeration 44 45 Everything starts with `certipy find`. Do this before anything else. 46 47 ```bash 48 # Full enumeration, only show vulnerable, print to terminal + save JSON/BloodHound 49 certipy-ad find -u 'lowpriv@domain.htb' -p 'Password123!' \ 50 -dc-ip $TARGET -vulnerable -stdout 51 52 # Pass-the-hash variant 53 certipy-ad find -u 'lowpriv@domain.htb' -hashes :NTHASH -dc-ip $TARGET -vulnerable -stdout 54 55 # Enumerate everything (not just vulnerable) — useful for THEFT/PERSIST target hunting 56 certipy-ad find -u 'lowpriv@domain.htb' -p 'Password123!' -dc-ip $TARGET -stdout 57 ``` 58 59 Three registry/patch checks decide whether the mapping-based attacks work: 60 61 ```bash 62 # StrongCertificateBindingEnforcement on the DC (0=off, 1=compat[default], 2=full) 63 nxc smb $TARGET -u user -p pass \ 64 -x 'reg query "HKLM\SYSTEM\CurrentControlSet\Services\Kdc" /v StrongCertificateBindingEnforcement' 65 66 # CertificateMappingMethods (ESC10 — 0x4 = weak UPN mapping enabled) 67 nxc smb $TARGET -u user -p pass \ 68 -x 'reg query "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel" /v CertificateMappingMethods' 69 70 # Patch level (Certifried KB5014754, EKUwu KB5044281) 71 nxc smb $TARGET -u user -p pass -x 'wmic qfe list brief | findstr "KB5014754 KB5044281"' 72 ``` 73 74 > **Tip — feed BloodHound.** `certipy find` writes a BloodHound-compatible zip. Import it to see ACL edges to PKI objects (drives ESC4/ESC5/ESC7 and DPERSIST3). 75 76 ## 3. Phase 2 — Escalation (ESC) 77 78 Classify the finding, then jump to the technique. The generic escalation loop is always the same three Certipy verbs: 79 80 ```bash 81 # 1. request a cert for a privileged identity (technique-specific flags) 82 certipy-ad req -u me -p pass -ca 'CA-NAME' -template 'TEMPLATE' -upn 'administrator@domain.htb' 83 # 2. authenticate the cert -> TGT + NT hash (PKINIT + UnPAC-the-hash) 84 certipy-ad auth -pfx administrator.pfx -dc-ip $TARGET 85 # 3. use the TGT or hash 86 export KRB5CCNAME=administrator.ccache && impacket-wmiexec -k -no-pass DC01.domain.htb 87 ``` 88 89 The ESC family answers "how do I get a cert for someone I shouldn't." Quick routing: 90 91 | If certipy shows... | Technique | 92 | :-- | :-- | 93 | `Enrollee Supplies Subject: True` + auth EKU | ESC1 — SAN specification in template | 94 | `Any Purpose` / `No EKU` | ESC2 — Any Purpose EKU / No EKU (the Swiss certificate) | 95 | `Certificate Request Agent` EKU | ESC3 — misconfigured enrollment agent templates | 96 | Dangerous ACE on template | ESC4 — vulnerable certificate template access control | 97 | Write ACE on PKI object | ESC5 — vulnerable PKI object access control | 98 | `User Specified SAN: Enabled` (CA) | ESC6 — EDITF_ATTRIBUTESUBJECTALTNAME2 flag | 99 | `ManageCA` / `ManageCertificates` | ESC7 — vulnerable CA access control | 100 | `Web Enrollment: Enabled` | ESC8 — NTLM relay to ADCS HTTP web enrollment | 101 | `No Security Extension` (template) | ESC9 — no security extension (template-level) | 102 | Weak DC mapping registry | ESC10 — weak certificate mapping | 103 | `IF_ENFORCEENCRYPTICERTREQUEST: Disabled` | ESC11 — NTLM relay to ADCS RPC (ICPR) | 104 | YubiHSM on CA | ESC12 — shell access to CA with YubiHSM | 105 | Issuance policy OID group link | ESC13 — issuance policy OID group link | 106 | Weak `altSecurityIdentities` mapping | ESC14 — weak explicit certificate mapping | 107 | Schema v1 + unpatched CA | ESC15 — EKUwu (CVE-2024-49019) | 108 | `DisableExtensionList` on CA | ESC16 — security extension disabled on CA (globally) | 109 | WSUS + Server Auth template | ESC17 — ADCS certificate spoofing vs HTTPS-enabled WSUS clients | 110 | MachineAccountQuota ≥ 1 + unpatched DC | Certifried (CVE-2022-26923) | 111 112 ## 4. Phase 3 — Credential Theft (THEFT) 113 114 Once you have a foothold, harvest certificates that already exist rather than requesting new ones. Often quieter than an ESC, and yields keys for users/machines that have already enrolled. 115 116 | Technique | Target | Note | 117 | :-- | :-- | :-- | 118 | THEFT1 — export cert + private key via CryptoAPI | Exportable (or force-exportable) keys in a user's store | | 119 | THEFT2 — user certificate theft via DPAPI | `%APPDATA%\Microsoft\Crypto` + masterkeys | | 120 | THEFT3 — machine certificate theft via DPAPI | SYSTEM store, machine masterkeys | | 121 | THEFT4 — finding certificate files on disk | `.pfx .p12 .pem .key`, unattend.xml | | 122 | THEFT5 — NT hash via PKINIT (UnPAC-the-hash) | Any cert you can authenticate with | | 123 124 ## 5. Phase 4 — Using Certificates (PKINIT) 125 126 A certificate with an authentication EKU is spent through **PKINIT** to obtain a Kerberos TGT, and optionally the account's NT hash via **UnPAC-the-hash** (U2U). This is the pivot between "I have a .pfx" and "I have a shell." 127 128 ```bash 129 # Certipy does PKINIT + UnPAC in one step 130 certipy-ad auth -pfx target.pfx -dc-ip $TARGET 131 # -> target.ccache (TGT) AND the account's NT hash 132 133 # Windows equivalent (Rubeus) 134 .\Rubeus.exe asktgt /user:target /certificate:target.pfx /getcredentials /nowrap 135 ``` 136 137 > **Warning — clock skew breaks PKINIT.** Certificate auth is Kerberos, so it is time-sensitive. If `certipy auth` throws `KRB_AP_ERR_SKEW`, wrap it with faketime: 138 > ```bash 139 > faketime -f '+7h30m' certipy-ad auth -pfx target.pfx -dc-ip $TARGET 140 > ``` 141 142 > **Tip — Pass-the-Certificate.** If the DC has no PKINIT support but does support Schannel/LDAPS, authenticate the cert over LDAP (`certipy auth -ldap-shell`) instead of Kerberos. Shadow Credentials (msDS-KeyCredentialLink abuse) uses the same PKINIT path with a key you plant yourself. 143 144 ## 6. Phase 5 — Persistence (PERSIST / DPERSIST) 145 146 Certificates make excellent persistence because they outlive password resets. 147 148 **Account persistence** — keep access to one or more principals: 149 150 | Technique | Idea | 151 | :-- | :-- | 152 | PERSIST1 — active user cert | Enrol/steal a long-life cert for a user you control | 153 | PERSIST2 — machine account cert | Cert for a computer account survives the 30-day machine password rotation | 154 | PERSIST3 — certificate renewal | Renew before expiry using the existing key, no creds needed | 155 156 **Domain persistence** — forge auth for *anyone*; requires you first reach the CA private key or high privilege: 157 158 | Technique | Idea | 159 | :-- | :-- | 160 | DPERSIST1 — Golden Certificate | Steal CA private key, forge certs offline | 161 | DPERSIST2 — Rogue CA / NTAuth | Add an attacker CA to `NTAuthCertificates` and Root store | 162 | DPERSIST3 — malicious misconfiguration | Plant ESC4/5/7-style ACL backdoors on PKI objects | 163 164 <figure class="flow plate corners"> 165 <figcaption class="flow__cap"><span class="flow__kind">Domain persistence routes</span><span class="flow__dir">LR</span></figcaption> 166 <div class="flow__body"> 167 <svg class="flow-svg" viewBox="0 0 685 330" role="img" aria-label="Domain Admin or CA server access branches into DPERSIST1 steal CA key, DPERSIST2 rogue CA in NTAuth, and DPERSIST3 ACL backdoor; the first two forge a cert for any principal offline, the third re-runs ESC4/5/7 at will"> 168 <path class="fedge" d="M185,180 L265,84" marker-end="url(#flow-arrow)" /> 169 <path class="fedge" d="M185,180 L265,180" marker-end="url(#flow-arrow)" /> 170 <path class="fedge" d="M185,180 L265,276" marker-end="url(#flow-arrow)" /> 171 <path class="fedge" d="M415,84 L495,132" marker-end="url(#flow-arrow)" /> 172 <path class="fedge" d="M415,180 L495,132" marker-end="url(#flow-arrow)" /> 173 <path class="fedge" d="M415,276 L495,276" marker-end="url(#flow-arrow)" /> 174 <g class="fnode is-entry"><rect class="fnode__box" x="35" y="156" width="150" height="48" /><text class="fnode__label" x="110" y="177" text-anchor="middle">Domain Admin /<tspan class="sub" x="110" dy="15">CA server access</tspan></text></g> 175 <g class="fnode"><rect class="fnode__box" x="265" y="60" width="150" height="48" /><text class="fnode__label" x="340" y="81" text-anchor="middle">DPERSIST1<tspan class="sub" x="340" dy="15">steal CA key</tspan></text></g> 176 <g class="fnode"><rect class="fnode__box" x="265" y="156" width="150" height="48" /><text class="fnode__label" x="340" y="177" text-anchor="middle">DPERSIST2<tspan class="sub" x="340" dy="15">rogue CA in NTAuth</tspan></text></g> 177 <g class="fnode"><rect class="fnode__box" x="265" y="252" width="150" height="48" /><text class="fnode__label" x="340" y="273" text-anchor="middle">DPERSIST3<tspan class="sub" x="340" dy="15">ACL backdoor</tspan></text></g> 178 <g class="fnode is-goal"><rect class="fnode__box" x="495" y="108" width="150" height="48" /><text class="fnode__label" x="570" y="129" text-anchor="middle">forge cert for<tspan class="sub" x="570" dy="15">any principal, offline</tspan></text></g> 179 <g class="fnode"><rect class="fnode__box" x="495" y="252" width="150" height="48" /><text class="fnode__label" x="570" y="273" text-anchor="middle">re-run ESC4/5/7<tspan class="sub" x="570" dy="15">at will</tspan></text></g> 180 </svg> 181 </div> 182 </figure> 183 184 ## 7. Attack Chaining 185 186 Real engagements chain these. Common paths: 187 188 <figure class="flow plate corners"> 189 <figcaption class="flow__cap"><span class="flow__kind">Common attack chains</span><span class="flow__dir">TD</span></figcaption> 190 <div class="flow__body"> 191 <div class="flow__diagram" data-dir="td"> 192 <div class="flow-rank"><div class="flow-node is-entry">Low-priv creds</div></div> 193 <div class="flow-edge"></div> 194 <div class="flow-rank"><div class="flow-node">certipy find</div></div> 195 <div class="flow-branches"> 196 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">vuln template</span></div><div class="flow-node">ESC1/ESC9/ESC15</div></div> 197 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">ACL edge</span></div><div class="flow-node">ESC4/ESC5 -> make a template vuln -> ESC1</div></div> 198 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">CA rights</span></div><div class="flow-node">ESC7 -> approve own request / ESC6</div></div> 199 <div class="flow-lane"><div class="flow-edge"><span class="flow-edge__label">relay vector</span></div><div class="flow-node">ESC8/ESC11 -> relay DC$ -> DA</div></div> 200 </div> 201 <div class="flow-join"></div> 202 <div class="flow-rank"><div class="flow-node">certipy auth -> DA TGT + hash</div></div> 203 <div class="flow-edge"></div> 204 <div class="flow-rank"><div class="flow-node is-goal">DPERSIST1/2/3<span class="sub">domain persistence</span></div></div> 205 </div> 206 </div> 207 </figure> 208 209 Worked chains: 210 211 - **ESC5 → ESC1:** you hold a write ACE over a PKI object, so you edit a template to enable `Enrollee Supplies Subject`, turning it into ESC1, then request an Administrator cert. 212 - **ESC8 → DA:** coerce the DC (PetitPotam) and relay its NTLM auth to the CA web-enrolment endpoint, minting a DC certificate, then DCSync. 213 - **ESC7 → ESC6-like:** with `ManageCA` you flip the `EDITF_ATTRIBUTESUBJECTALTNAME2` flag on the CA, enabling SAN injection on any template. 214 - **Shadow Credentials → PKINIT:** you have `GenericWrite` over a target, so you plant a Key Credential and authenticate as them without touching ADCS templates at all. 215 216 ## 8. Defence & Detection 217 218 > **Detection focus —** Certificate abuse is quiet at the AD layer but leaves CA and KDC traces. 219 220 - **CA logs:** Event ID **4886** (request) and **4887** (issued). Alert when the SAN/UPN differs from the requester. 221 - **KDC logs:** Event ID **4768** (TGT) with certificate info; a 4768 without a preceding smart-card enrolment is suspicious. 222 - **Enforce strong binding:** `StrongCertificateBindingEnforcement = 2` on all DCs (blocks ESC1/6/9/10/16 UPN tricks). 223 - **Audit PKI ACLs:** review `NTAuthCertificates`, the Enrollment Services container, CA objects, and template DACLs for non-admin write (kills ESC4/5/7 and DPERSIST3). 224 - **Restrict enrolment:** remove `Domain Users` / `Authenticated Users` from authentication-capable templates; require manager approval where SAN is needed. 225 - **Protect the CA key:** HSM-backed keys defeat DPERSIST1 offline forgery. 226 - **Monitor** `msDS-KeyCredentialLink` writes (Shadow Credentials) and `altSecurityIdentities` writes (ESC14). 227 228 ## 9. Full Technique Map 229 230 | Phase | Techniques | Prereq | Outcome | 231 | :-- | :-- | :-- | :-- | 232 | **Escalate** | ESC1–17, Certifried | Low-priv + a misconfig/bug/ACL | Cert for a privileged identity | 233 | **Steal** | THEFT1–5 | Host foothold | Existing keys / NT hash | 234 | **Use** | PKINIT auth | A `.pfx` with auth EKU | TGT + NT hash → shell | 235 | **Persist (account)** | PERSIST1–3 | Enrolment or a stolen cert | Long-life access to principals | 236 | **Persist (domain)** | DPERSIST1–3 | CA key or high privilege | Forge auth for anyone | 237 | **Adjacent** | Shadow Credentials | `GenericWrite`/`GenericAll` over target | Auth as target via planted key | 238 239 ## Sources 240 241 - SpecterOps *Certified Pre-Owned* (Schroeder & Christensen) 242 - Certipy Wiki — https://github.com/ly4k/Certipy/wiki 243 - The Hacker Recipes — ADCS — https://www.thehacker.recipes/ad/movement/ad-cs/