Ensure that System Accounts Do Not Run a Shell Upon Login
Ensures every non-root system account (UID < 1000) has a non-login shell, denying interactive sessions to service accounts.
Checked against what is installed or registered, packages present/absent, account databases.
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
System accounts (UID below 1000: daemon, bin, www-data, mysql, …) exist to own files and run services, not to be logged into. If such an account keeps an interactive login shell (e.g. /bin/bash), it becomes a usable foothold: an attacker who compromises the matching service, or who guesses/cracks the account, can open a real session. Every system account other than root should have a non-login shell (nologin/false) or a purpose-built one (sync/shutdown/halt).
What Pavois checks
Pavois reads each account's UID and its effective login shell (field 7 of the passwd database) and flags any system account (UID < 1000, not root) whose shell is not nologin/false or a benign special shell (sync/shutdown/halt). Evaluating the resolved passwd entry reflects exactly what the system would grant at login, including accounts provisioned by packages.
describe command('awk -F: \'($3<1000 && $1!="root" && $7 !~ /nologin|false|sync|shutdown|halt/){print $1}\' /etc/passwd') do
its('stdout.strip') { should eq '' }
endHow to verify it is applied
List any system account with an interactive shell:
awk -F: '($3<1000 && $1!="root" && $7 !~ /nologin|false|sync|shutdown|halt/){print $1":"$7}' /etc/passwd, expected output is empty.getent passwd <account>, the shell field (last column) should read/usr/sbin/nologinor/bin/false.
Inspect & investigate
Session attempts and shell changes are logged by the auth stack:
grep -E 'session opened|chsh|usermod' /var/log/auth.log(Debian/Ubuntu) //var/log/secure(RHEL), shows any session opened for a system account and any shell modification.getent passwd <account>is the authoritative current-state probe of the assigned shell.
Remediation
No automated harden plan is defined for this rule yet, so it must be applied manually: for each flagged account set a non-login shell with usermod -s /usr/sbin/nologin <account> (use /sbin/nologin on RHEL). Do not touch root, and leave the special sync/shutdown/halt accounts as they are.
Pavois applies this with its own harden engine, the plan below, not a shell script:
| command | awk -F: '($3<1000 && $1!="root" && $7 !~ /nologin|false|sync|shutdown|halt/){print $1}' /etc/passwd | while read u; do usermod -s /usr/sbin/nologin "$u" 2>/dev/null; done; true |
|---|---|
| name | nologin-sysaccounts |
| not_if | test -z "$(awk -F: '($3<1000 && $1!="root" && $7 !~ /nologin|false|sync|shutdown|halt/){print $1}' /etc/passwd)" |
| resource | exec |
pavois harden plan localwhere the target is local, a user@host SSH alias, or a container , Docs
Impact & precautions
Risk if not applied: a service account with a real shell is a ready-made foothold for lateral movement and persistence.
Precautions before applying: confirm the account is genuinely non-interactive, some accounts (e.g. a CI or backup user, or a DB account used for scheduled su - <user> -c ... maintenance) legitimately need a shell. Changing the shell of such an account will break those jobs. Verify the owning service still starts and that no cron/systemd unit runs as that user with a login shell before switching it to nologin.
Standards mapping
| Standard | Reference | Type | Version | Confidence |
|---|---|---|---|---|
| CIS | 5.4.2.7, 8.2.2 | direct | per OS, see the benchmark table | high |
| NIST | AC-6, CM-6, CM-6(a), CM-6(b) | supporting | 800-53 Rev 5 · 800-171 Rev 2 (pinned) | medium |
| PCI DSS | 8.2.2 | supporting | 4.0.1 | medium |
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.