Skip to content

PersistenceUser activity

/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

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

FileContentMode (typical)
/etc/passwdname, UID, GID, GECOS, home, shellworld-readable
/etc/shadowhash and aging per accountroot only (Debian: group shadow may read)
/etc/groupgroup name, GID, membersworld-readable
/etc/gshadowgroup password, admins, membersroot only
/etc/passwd-, /etc/shadow-, /etc/group-, /etc/gshadow-previous version, written by shadow-utils toolssame as original
/etc/login.defsUID_MIN, SYS_UID_MIN, ENCRYPT_METHOD, aging defaultsworld-readable
/etc/default/useradd, /etc/skel/defaults and template home filesworld-readable
/etc/subuid, /etc/subgidsubordinate 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):

#FieldNotes
1login name
2encrypted password$y$ yescrypt, $6$ SHA-512, $5$ SHA-256, $1$ MD5; ! prefix = locked; * or ! alone = no password login; empty = no password needed
3date of last password changedays since 1970-01-01; 0 = must change at next login; empty = aging disabled
4minimum password agedays
5maximum password agedays
6warning perioddays
7inactivity perioddays after expiry
8account expiration datedays since 1970-01-01; empty = never
9reserved

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/bash or /bin/sh and a valid hash are a strong lead, especially www-data, nobody or service users.
  • A UID in the user range with a home in /tmp, /dev/shm or /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 useradd log lines around a change that the file times show suggests manual editing, which is itself suspicious.
  • lastlog and faillog entries for new UIDs are reset by useradd; see wtmp, btmp and lastlog.

See also