Skip to content

Credential Hunting

When the privilege/token/group/ACL checks don’t produce a primitive, hunt for credentials someone left on disk. A password for a local admin, a domain account, or a service sitting in a config file, a script, a history file, or a note turns “I can read files” into a higher-privileged context - or a domain foothold. This is the lowest-value, highest-effort lookup, so it comes last; but re-run it after every escalation, because SYSTEM can read files your foothold user couldn’t.

Terminal window
# Config files - recursive string hunt
findstr /SIM /C:"password" *.txt *.ini *.cfg *.config *.xml
Get-ChildItem C:\ -Recurse -Include *.config,*.xml,*.ini,*.rdp,*.vnc,*.cred -EA Ignore
# High-value fixed locations
gc (Get-PSReadLineOption).HistorySavePath # PowerShell history
type C:\Windows\Panther\unattend.xml # install answer file
gc 'C:\Users\*\AppData\Local\Google\Chrome\User Data\Default\Custom Dictionary.txt'
# DPAPI-protected PowerShell credential (if running AS that user)
$c = Import-Clixml C:\scripts\pass.xml; $c.GetNetworkCredential().Password
# Network shares, at scale
.\Snaffler.exe -s -o snaffler.log # crawls shares for *.kdbx, *.ppk, passwords.*

Success indicator: a recovered cleartext password (or crackable hash / private key) you can reuse for a higher-privileged local or domain account - confirm with runas, WinRM, or an authenticated service logon.

Credential hunting is the fallback, not the opener - a full file-system sweep is hours of work and the lowest hit-rate step in the loop (see the overview’s operational sequence). Reach for it when:

  • Token privileges, group memberships, missing patches, and service/ACL audits have all come up empty.
  • You’ve just escalated and should re-sweep: SYSTEM or a new admin context can read profile paths, registry hives, and share folders your previous user couldn’t. Always recheck PowerShell history and user profiles after gaining local admin.
  • You have a domain foothold and want to pivot: a password on a share or in a script is frequently the path from local access to a domain account.

Against best practice, applications routinely store passwords in cleartext config files. findstr is the workhorse for searching file contents recursively:

Terminal window
findstr /SIM /C:"password" *.txt *.ini *.cfg *.config *.xml
  • /S recurse subdirectories · /I case-insensitive · /M print only the filename · /C:"..." literal search string.
  • Drop /M to see the matching line (findstr /SI "password" *.config), add /N for line numbers, /P to skip binary files.

IIS stores credentials (connection strings, app-pool identities) in web.config. The default site’s is at C:\inetpub\wwwroot\web.config, but there are often several across sites and application directories:

Terminal window
Get-ChildItem C:\ -Recurse -Filter web.config -EA Ignore | Select FullName
where.exe /R C:\ web.config # where.exe, not `where` - that's the Where-Object alias in PS

To search for a file by name regardless of contents:

Terminal window
dir /s /b confidential.txt
Terminal window
Get-ChildItem -Path C:\ -Recurse -Filter "confidential.txt" -ErrorAction SilentlyContinue

The two orthogonal sweeps - by content and by extension - between them catch most at-rest secrets. By content:

Terminal window
:: match + show the line
findstr /si password *.xml *.ini *.txt *.config
:: every file, with line numbers (/p skips binaries)
findstr /spin "password" *.*
Terminal window
Select-String -Path C:\Users\*\Documents\*.txt -Pattern password

By extension - the names that tend to hold secrets:

Terminal window
dir /S /B *pass*.txt == *pass*.xml == *pass*.ini == *cred* == *vnc* == *.config
Terminal window
Get-ChildItem C:\ -Recurse -Include *.rdp,*.config,*.vnc,*.cred,*.kdbx,*.ppk -ErrorAction Ignore

Some stashes are predictable enough to check directly rather than sweep for.

Since PowerShell 5.0 (Windows 10+), PSReadLine logs command history to a per-user file. Commands that pass credentials on the command line (net use, wevtutil ... /u: /p:, scripted logons) end up recorded in cleartext:

Terminal window
(Get-PSReadLineOption).HistorySavePath
# C:\Users\<user>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
gc (Get-PSReadLineOption).HistorySavePath

Once you’re local admin, sweep every user’s history at once (assumes the default path):

Terminal window
foreach($u in (ls C:\users).fullname){
cat "$u\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt" -EA SilentlyContinue
}

unattend.xml / sysprep.xml / autounattend.xml can carry an AutoLogon password or extra-account credentials, stored plaintext or base64. They should be deleted after imaging, but copies linger in C:\Windows\Panther\, C:\Windows\System32\Sysprep\, and ad-hoc image-build folders:

<AutoLogon>
<Password><Value>local_4dmin_p@ss</Value><PlainText>true</PlainText></Password>
<Enabled>true</Enabled><Username>Administrator</Username>
</AutoLogon>

A <PlainText>false</PlainText> value is only base64, not encryption - decode it.

Admins store “encrypted” credentials for scripts with Get-Credential | Export-Clixml. The protection is DPAPI - the blob decrypts only as the same user on the same machine. So if you already have code execution as that user, reversing it is trivial:

Terminal window
$c = Import-Clixml -Path 'C:\scripts\pass.xml'
$c.GetNetworkCredential().UserName
$c.GetNetworkCredential().Password # Str0ng3ncryptedP@ss!

If you find the .xml but aren’t that user, you can’t just import it - you’d need the user’s DPAPI masterkey (recoverable as SYSTEM, or with the domain DPAPI backup key) via mimikatz dpapi:: or SharpDPAPI. Treat the file as a lead even when the direct import fails.

Users add words their spell-checker flags to a custom dictionary - and people type passwords into fields that get spell-checked. Chrome’s is plaintext:

Terminal window
gc 'C:\Users\<user>\AppData\Local\Google\Chrome\User Data\Default\Custom Dictionary.txt' | Select-String -Pattern '!|@|[0-9]'

The Windows Sticky Notes app is a SQLite database, not a text file, so people treat it as a scratchpad for passwords without realizing it persists. It lives at:

C:\Users\<user>\AppData\Local\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe\LocalState\plum.sqlite

Copy the three plum.sqlite* files (.sqlite, -shm, -wal) off and read the Text column of the Note table - the -wal write-ahead log often holds notes not yet merged into the main DB:

Terminal window
Import-Module .\PSSQLite.psd1
Invoke-SqliteQuery -Database .\plum.sqlite -Query "SELECT Text FROM Note" | ft -wrap
# or, quick and dirty, off the box:
strings plum.sqlite-wal | findstr /i "pass root admin"

A grab-bag worth checking by hand - registry-hive copies yield local hashes (see Backup Operators for extracting them), logs and config dumps hold connection strings:

%WINDIR%\repair\sam %WINDIR%\repair\system %WINDIR%\System32\config\*.sav
%WINDIR%\Panther\unattend.xml %WINDIR%\debug\NetSetup.log
%WINDIR%\system32\CCM\logs\*.log %USERPROFILE%\ntuser.dat
%SYSTEMDRIVE%\pagefile.sys C:\ProgramData\Configs\*

In a domain, the richest hunting ground is file shares - user home folders with loose ACLs (every Domain User can read everyone’s folder), departmental shares, backup locations. Snaffler crawls accessible shares and flags interesting files by extension and content:

.\Snaffler.exe -s -o snaffler.log # -s console output, -o log file

It surfaces .kdbx (KeePass), .ppk (PuTTY key), .vmdk/.vhdx (virtual disks you can mount for the local SAM), .config, SSH private keys, and files whose names or contents look credential-bearing. A single password on a forgotten share is a common initial-access-or-escalation win.

  • Never store credentials in config files, scripts, or answer files; use a secrets manager or managed identities (gMSA) and reference secrets at runtime.
  • Delete unattend.xml/sysprep answer files after imaging, and scrub image-build shares. Audit C:\Windows\Panther\.
  • Tighten share ACLs - home folders should be readable only by their owner and admins, not all Domain Users. Run Snaffler yourself to find what an attacker would.
  • Disable PowerShell plaintext history for privileged automation, or keep secrets out of command lines (prompt, or read from a vault) so they never reach ConsoleHost_history.txt.
  • Monitor for mass file reads of profile/AppData paths and for findstr/Select-String sweeps across the file system.
  • Initial enumeration - flags installed password managers and profile paths that feed this hunt
  • Stored credentials - extracting secrets held by apps and the OS (browsers, cmdkey, registry, wifi, KeePass cracking)
  • Backup Operators - turning a recovered SAM/SYSTEM hive copy into local hashes
  • SeTakeOwnership - reading a credential file whose ACL denies your user