Passwords and Shells¶
/etc/shadow holds each account's password hash and aging fields. /etc/passwd holds the shell. Between them they decide whether an account can log in and how.
Blank and locked passwords¶
sudo awk -F: '($2 == "") {print $1}' /etc/shadow
Anything printed is an account with no password. Set one now:
sudo passwd bob
Locked accounts show ! at the start of the hash. sudo passwd -S bob prints L for locked, P for password set, NP for no password. Unlock an authorized user with sudo usermod -U bob.
Change passwords the README asks for¶
A password the README describes as weak, shared, or written down gets changed. So does every password on an account you found with a blank one. sudo passwd user sets it; sudo passwd -e user forces a change at next login so the person picks their own.
Hash algorithm¶
The hash prefix says how the password was hashed: $y$ is yescrypt (the default on Mint 21 and Debian 12), $6$ is SHA-512, $1$ is MD5 and $5$ is SHA-256, both weak enough to crack quickly. An old prefix on a live account means the password was set under a weak setting; the fix is on the Password Quality and Hashing page, and then the password is reset so it gets re-hashed.
sudo awk -F: '$2 ~ /^\$(1|5)\$/ {print $1}' /etc/shadow
Per-user password aging¶
/etc/login.defs sets the defaults for new accounts (Login Definitions). Existing accounts keep whatever they had, so each authorized user is checked and fixed with chage:
| Field | Meaning | Value |
|---|---|---|
| Maximum days | How long a password lasts | 30 to 90 (CIS 5.3.1.1 allows up to 365, Level 2) |
| Minimum days | How soon it can be changed again | 10 to 30 (blocks cycling straight back to the old password; CIS 5.3.1.2 asks for at least 1, Level 2) |
| Warning days | Notice before expiry | 7 to 14 (CIS 5.3.1.3: at least 7) |
| Inactive days | Days after expiry before the account locks | 45 or fewer (CIS 5.3.1.5, Level 2) |
sudo chage -l alice
sudo chage -M 90 -m 10 -W 14 -I 45 alice
A README sometimes describes a temporary account, a contractor or a visitor. The STIG (UBTU-22-411040) wants those to expire on their own within 72 hours: sudo chage -E $(date -d +3days +%F) tempuser.
Also check the "Last password change" line: a date in the future (CIS 5.3.1.6) means someone edited /etc/shadow by hand to dodge expiry. sudo chage -d 0 user forces a change at next login and fixes it.
Do it for each human account, checking with chage -l after each. Once you've done three by hand, the repetition is the lesson: the account list from the List and Compare page is one awk command, and a for loop that runs chage on each name it prints is your first account-hardening tool. Write it, run it on the practice image, and keep it.
sudo useradd -D -f 45 # the inactive default for accounts created later
Shells¶
Field 7 of /etc/passwd. Humans get /bin/bash. Service accounts get /usr/sbin/nologin (or /bin/false). Two things to look for: a human whose shell was changed to something odd (/bin/sh is fine; /usr/bin/screen or a script in /tmp is not), and a service account that was given bash.
awk -F: '{print $7}' /etc/passwd | sort | uniq -c
sudo usermod -s /bin/bash alice
sudo usermod -s /usr/sbin/nologin www-data
Every shell in use must also be listed in /etc/shells; one that isn't may be a planted binary. /etc/shells itself must not list nologin (CIS 5.3.3.1, Level 2), because programs that consult it (chsh, FTP servers) treat a listed shell as a login shell.
Shell timeout and umask¶
Two defaults for every shell session, both in a file under /etc/profile.d/ (CIS 5.3.3.2 and 5.3.3.3):
printf '%s\n' 'typeset -xr TMOUT=900' | sudo tee /etc/profile.d/50-tmout.sh
printf '%s\n' 'umask 027' | sudo tee /etc/profile.d/60-umask.sh
sudo sed -i 's/^UMASK.*/UMASK\t\t027/' /etc/login.defs
TMOUT=900 logs out an idle terminal after 15 minutes (STIG UBTU-22-412030 as well), read-only so a user can't unset it. umask 027 means new files aren't readable by everyone. Two more STIG items live in the same neighbourhood: * hard maxlogins 10 at the top of /etc/security/limits.conf caps concurrent sessions per account (UBTU-22-412020), and the vlock package lets a user lock their own terminal (UBTU-22-412025): sudo apt install -y vlock. Root's own ~/.bashrc and ~/.profile shouldn't set a looser umask (5.3.2.6), and /etc/profile.d/*.sh shouldn't either.
Home directories¶
Each human user's home directory exists, is owned by them, and is mode 750 or tighter (CIS 7.2.9). Their dot files (.bashrc, .profile) are owned by them and not writable by others (7.2.10); a .forward or .rhosts file is removed.
for u in $(awk -F: '$3 >= 1000 && $7 !~ /nologin|false/ {print $1":"$6}' /etc/passwd); do n=${u%%:*}; h=${u##*:}; [ -d "$h" ] || echo "$n has no home"; stat -c '%a %U %n' "$h"; done
sudo find /home -maxdepth 2 -name '.*' -type f \( -perm /022 -o -name .forward -o -name .rhosts \)
Verify¶
sudo awk -F: '($2 == "") {print $1}' /etc/shadow # nothing
sudo chage -l alice | grep -E 'Maximum|Minimum|warning'
getent passwd alice | cut -d: -f7
Example¶
sudo passwd -S for each user shows bob NP: no password. sudo passwd bob. sudo chage -l carol shows Maximum 99999: never expires. sudo chage -M 90 -m 10 -W 14 carol. getent passwd www-data ends in /bin/bash; the web server's account shouldn't have a shell: sudo usermod -s /usr/sbin/nologin www-data.
Try it¶
- Give a test user a blank password with
passwd -d, find it with theawkon this page, fix it. - Set aging on one user with
chageand read it back withchage -l.
Build it¶
A chage loop over the human accounts, and a report mode that prints chage -l for each. Add the blank-password and hash-algorithm checks from this page to accounts.sh.