/etc/passwd, shadow and group: Linux Local Accounts
Linux local account databases (passwd, shadow, group, gshadow and their backups) that reveal rogue accounts, UID 0 clones and password change dates.
- Location
- /etc/passwd, /etc/shadow, /etc/group, /etc/gshadow
- Proves
- Which local accounts and group memberships exist, which can log in, and when each password was last changed
- Timestamps
- shadow: days since 1970-01-01 UTC; files: inode mtime/ctime; logs: syslog/journal time
- Access
- passwd and group: any user; shadow and gshadow: root
- Retention
- Until changed; one backup generation (passwd-, shadow-, group-, gshadow-)
- Collection
- UAC, Velociraptor, cp -a, tar
Tools
Compare all tools- awkCLI · built into Linux
- pwck / grpckCLI · built into Linux
- VelociraptorPlatform · open source
- ausearchCLI · built into Linux
What it is
Four colon-separated text files define local users and groups. /etc/passwd maps names to UIDs, primary GIDs, home directories and login shells. /etc/shadow holds the password hash and password aging data. /etc/group and /etc/gshadow list groups, their members and group administrators. Directory-backed accounts (LDAP, SSSD, FreeIPA, Active Directory) do not appear here, so check /etc/nsswitch.conf for other sources.
Creating an account, adding a user to sudo or wheel, giving a service account a shell, or cloning UID 0 under an innocent name are classic persistence moves (MITRE T1136.001). The files, their backups, their inode times and the logging done by shadow-utils together show what changed and when.
Where it lives
| File | Content | Mode (typical) |
|---|---|---|
/etc/passwd | name, UID, GID, GECOS, home, shell | world-readable |
/etc/shadow | hash and aging per account | root only (Debian: group shadow may read) |
/etc/group | group name, GID, members | world-readable |
/etc/gshadow | group password, admins, members | root only |
/etc/passwd-, /etc/shadow-, /etc/group-, /etc/gshadow- | previous version, written by shadow-utils tools | same as original |
/etc/login.defs | UID_MIN, SYS_UID_MIN, ENCRYPT_METHOD, aging defaults | world-readable |
/etc/default/useradd, /etc/skel/ | defaults and template home files | world-readable |
/etc/subuid, /etc/subgid | subordinate ID ranges (rootless containers) | world-readable |
Paths are the same on Debian/Ubuntu and RHEL/Fedora. The admin group differs: sudo on Debian/Ubuntu, wheel on RHEL/Fedora.
What it proves
- Every local account, its UID, and whether it has an interactive shell.
- Accounts that can use a password (valid hash) versus locked (
!prefix) or no-password-login (*,!) accounts, and accounts with an empty password field, which can log in without one. - Additional UID 0 accounts: any name with UID 0 is root.
- The day each password was last changed, and forced expiry dates.
- Group memberships that grant privileges:
sudo,wheel,adm,docker,lxd,disk,shadow. - What changed recently, by diffing against the
-backup. - It does not show when an account was used; use wtmp and lastlog, auth logs and SSH artifacts for that.
Key fields
/etc/passwd: name:password:UID:GID:GECOS:home:shell. The second field is normally x (hash in shadow). A real hash here bypasses shadow and is suspicious.
/etc/shadow (nine fields):
| # | Field | Notes |
|---|---|---|
| 1 | login name | |
| 2 | encrypted password | $y$ yescrypt, $6$ SHA-512, $5$ SHA-256, $1$ MD5; ! prefix = locked; * or ! alone = no password login; empty = no password needed |
| 3 | date of last password change | days since 1970-01-01; 0 = must change at next login; empty = aging disabled |
| 4 | minimum password age | days |
| 5 | maximum password age | days |
| 6 | warning period | days |
| 7 | inactivity period | days after expiry |
| 8 | account expiration date | days since 1970-01-01; empty = never |
| 9 | reserved |
Debian 11, Ubuntu 22.04 and Fedora 35 made yescrypt the default for new passwords, through PAM. A $6$ hash among $y$ hashes means that password was set through a different path (before an upgrade, by a tool that follows ENCRYPT_METHOD in login.defs, or pasted in with usermod -p), which is worth explaining.
/etc/group: name:password:GID:member1,member2. /etc/gshadow: name:password:admins:members.
shadow-utils log lines (auth facility, so auth.log/secure and the journal):
useradd[2210]: new group: name=backup2, GID=1002
useradd[2210]: new user: name=backup2, UID=1002, GID=1002, home=/home/backup2, shell=/bin/bash, from=/dev/pts/0
usermod[2231]: add 'backup2' to group 'sudo'
usermod[2231]: add 'backup2' to shadow group 'sudo'
passwd[2240]: pam_unix(passwd:chauthtok): password changed for backup2
userdel[2301]: delete user 'backup2'
Other messages include change user '%s' shell from '%s' to '%s', change user '%s' UID from ..., lock user '%s' password and group added to /etc/group: name=.... When built with audit support, the tools also emit ADD_USER, ADD_GROUP, DEL_USER, USER_MGMT and USER_CHAUTHTOK records to audit.log.
Timestamps
Shadow fields 3 and 8 are day counts, not seconds. Convert with:
date -u -d @$((19985 * 86400)) +%F # -> 2024-09-19
awk -F: '$3 ~ /^[0-9]+$/ {print $1, strftime("%F", $3*86400, 1)}' shadow # gawk
The day granularity means you only get a date. File-level times add precision: the mtime and ctime of passwd, shadow and group (and the backup files) reflect the last write by any tool. On ext4 the birth time (see crtime) changes too, because shadow-utils writes a new file and renames it over the old one.
The backups carry a useful quirk: when shadow-utils creates passwd- (and the other backups) it copies the atime and mtime of the file being replaced. So the backup's mtime is the time of the previous change, while its ctime and birth time mark the latest change. Two change times from one file pair.
Retention
The files keep only the current state. Each backup holds exactly one previous version, overwritten at the next change by a shadow-utils tool (useradd, usermod, passwd, vipw, chpasswd). Editing with a text editor, sed -i or a script does not refresh the backup, so the backup may be much older than the last change. The log lines follow auth log and journal retention.
Collection
UAC's /etc collection deliberately excludes shadow, shadow-, gshadow and gshadow-. If hashes and aging data are in scope, copy them separately under your legal authority and handle them as sensitive:
E=/mnt/evidence
tar -C "$E" -cpf /cases/2026-017/accounts.tar \
etc/passwd etc/passwd- etc/shadow etc/shadow- etc/group etc/group- \
etc/gshadow etc/gshadow- etc/login.defs etc/default/useradd etc/subuid etc/subgid etc/sudoers etc/sudoers.d
stat "$E"/etc/{passwd,shadow,group}* # record inode times before anything else
Velociraptor offers Linux.Sys.Users, Linux.Sys.Groups and Linux.Users.RootUsers. On a live host, getent passwd also shows directory accounts.
Parsing
# UID 0 clones and accounts with login shells
awk -F: '$3 == 0 {print "UID0:", $1}' passwd
awk -F: '$7 !~ /(nologin|false|sync|shutdown|halt)$/ {print $1, $3, $6, $7}' passwd
# Empty password fields and non-x passwd fields
awk -F: '$2 == "" {print "EMPTY:", $1}' shadow
awk -F: '$2 != "x" {print "HASH IN PASSWD:", $1}' passwd
# Duplicate UIDs and names, privileged group members
cut -d: -f3 passwd | sort | uniq -d
grep -E '^(sudo|wheel|adm|docker|lxd|disk|root):' group
# What changed since the backup
diff passwd- passwd; diff group- group
# Consistency check without writing (-r = read-only)
pwck -r -R /mnt/evidence; grpck -r -R /mnt/evidence
Security checks here identify accounts; do not attempt to recover passwords from hashes as part of triage.
Investigator tips
- System accounts (UID below
UID_MIN, usually 1000) with/bin/bashor/bin/shand a valid hash are a strong lead, especiallywww-data,nobodyor service users. - A UID in the user range with a home in
/tmp,/dev/shmor/var/tmp, or a name mimicking a daemon (systemd-network2,sshd_), deserves review. - Cross-check new accounts with SSH keys in their home, first-use sudo markers in sudo logs, and login records.
- A shadow change date after the incident start for root or service accounts means someone set a password.
- Absence of
useraddlog lines around a change that the file times show suggests manual editing, which is itself suspicious. lastlogandfaillogentries for new UIDs are reset byuseradd; see wtmp, btmp and lastlog.