Prevent Login to Accounts With Empty Password
Remove the nullok option from every pam_unix.so line in /etc/pam.d/. With nullok, PAM accepts an empty password as valid authentication: any account whose shadow hash field is blank can be logged into without supplying anything.
Checked against the content of a persistent configuration file, the source of truth that survives reboots.
A mapping is a cross-reference to where each standard places this requirement, anchored and cross-validated, not a claim of equivalence. A passing check is evidence toward these references, how to read it.
Why this rule matters
If an account has an empty password, anyone could log in and run commands with the privileges of that account. Accounts with empty passwords should never be used in operational environments.
What Pavois checks
Pavois greps recursively across the whole /etc/pam.d/ stack (grep -RlE '^[^#]*\bpam_unix\.so\b[^#]*\bnullok') for any non-commented pam_unix.so line carrying the nullok option, and fails as soon as one file matches. Scanning the entire directory, not just common-auth or system-auth, is what catches a nullok re-introduced in a secondary service stack (login, sshd, su, a vendor drop-in). The sssd-shadowutils stack is excluded: there nullok has a different meaning and is not an empty-password bypass.
describe command('grep -RlE \'^[^#]*\bpam_unix\.so\b[^#]*\bnullok\' /etc/pam.d/ 2>/dev/null | grep -qv \'sssd-shadowutils\' && echo ko || echo ok') do
its('stdout.strip') { should eq 'ok' }
endHow to verify it is applied
Run the same search and expect no output:
grep -RE '^[^#]*pam_unix\.so.*nullok' /etc/pam.d/
An offending line looks like auth [success=1 default=ignore] pam_unix.so nullok; once fixed it reads auth [success=1 default=ignore] pam_unix.so. Cross-check that no account actually carries an empty hash: sudo awk -F: '($2 == ""){print $1}' /etc/shadow must print nothing.
Inspect & investigate
An empty-password login accepted thanks to nullok looks like an ordinary success in /var/log/auth.log (or journalctl -u sshd), with nothing marking it as passwordless, which is exactly why the option is dangerous:
sshd[5678]: Accepted password for backup from 203.0.113.10 port 55012 ssh2
Once the option is removed, the attempt is refused and PAM logs the failure:
sshd[5678]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=203.0.113.10 user=backup
auditd records the same rejection as a USER_AUTH ... res=failed event in /var/log/audit/audit.log.
Remediation
The Pavois harden plan runs an exec remediation named pam-unix-remove-nullok: it lists the /etc/pam.d/ files whose pam_unix.so lines carry nullok and strips the token in place with sed -ri '/pam_unix\.so/ s/[[:space:]]*\bnullok\b//g', leaving the rest of each line untouched (no module reordering). A not_if guard skips the step when no match remains, so the apply is idempotent. On Debian and Ubuntu, /etc/pam.d/common-auth and common-password are generated by pam-auth-update from the profiles in /usr/share/pam-configs/: a direct edit holds only until pam-auth-update runs again (a libpam-runtime upgrade or reconfiguration), which restores nullok. Re-run pavois harden apply after such an upgrade, or drop nullok from the unix profile so the change survives regeneration.
Pavois applies this with its own harden engine, the plan below, not a shell script:
| command | grep -rlE '^[^#]*pam_unix\.so.*nullok' /etc/pam.d/ | while read -r f; do sed -ri '/pam_unix\.so/ s/[[:space:]]*\bnullok\b//g' "$f"; done; true |
|---|---|
| name | pam-unix-remove-nullok |
| not_if | ! grep -rqE '^[^#]*pam_unix\.so.*nullok' /etc/pam.d/ |
| resource | exec |
pavois harden plan localwhere the target is local, a user@host SSH alias, or a container , Docs
Impact & precautions
Keeping nullok turns any account left with an empty hash (a mis-provisioned service account, a user created without a password) into a passwordless entry point, possibly one with sudo rights. Removing the option is normally safe: accounts with a real password are unaffected, and no existing session is cut. The lockout risk is concentrated on the accounts that were relying on the empty password: they are refused immediately, including at the console. Before applying, list them with sudo awk -F: '($2 == ""){print $1}' /etc/shadow; if an administrative account is on that list, set a real password (passwd <user>) or install an SSH key first, and keep a second privileged session open during the apply. Editing PAM is the classic way to lock yourself out of a host, so confirm access from a new session before closing the current one. Finally, removing nullok does not fix the accounts themselves: pair it with locking every empty-password account (passwd -l <user>).
Standards mapping
| Standard | Reference | Type | Version | Confidence |
|---|---|---|---|---|
| CIS | 5.3.3.4.1 | direct | per OS, see the benchmark table | high |
Each reference is a cross-reference anchored in the upstream benchmark and cross-validated against the SCAP Security Guide and ansible-lockdown, not a claim of equivalence. Direct = a prescriptive, line-level requirement; supporting = an abstract control family (NIST) the check provides evidence toward. How to read a mapping.