Description
System accounts (UID below UID_MIN, 1000 on Debian) run services and own files. They do not
need to log in. Their shell must be /usr/sbin/nologin or /bin/false, and their password
must be locked. root, sync, shutdown and halt are the exceptions.
Accounts are read from /etc/passwd only; network directories (LDAP, SSSD) are out of scope.
Rationale
A system account with a valid shell can be used to log in if it ever gets a password or an SSH key, and attackers like to add access to such accounts because nobody checks them.
Impact
Services keep working: they do not use a login shell. Administrators who need a shell as a
service account use sudo -u <account> -s /bin/bash.
Audit
awk -F: '$3 < 1000 && $1 !~ /^(root|sync|shutdown|halt)$/ {print $1, $7}' /etc/passwd: every shell must be/usr/sbin/nologin,/sbin/nologinor/bin/false.- Every account with such a non-login shell must show
Lwithpasswd -S.
Automated: every check must pass. Scanners report each check by its id.
shells
System accounts with a login shell.
script: |
bad=$(awk -F: '$3 < 1000 && $1 !~ /^(root|sync|shutdown|halt)$/ && $7 !~ /^(\/usr)?\/sbin\/nologin$|^\/bin\/false$/ {print $1 ":" $7}' /etc/passwd)
echo "${bad:-none}"
[ -z "$bad" ]
locked
Accounts without a login shell that are not locked.
script: |
bad=
for u in $(awk -F: '$1 != "root" && $7 ~ /nologin$|^\/bin\/false$/ {print $1}' /etc/passwd); do
s=$(passwd -S "$u" 2>/dev/null | cut -d' ' -f2)
[ "$s" = L ] || bad="$bad $u:$s"
done
echo "${bad:-none}"
[ -z "$bad" ]
Remediation
- For each listed system account:
usermod -s /usr/sbin/nologin <account>. - For each account without a login shell that is not locked:
usermod -L <account>.
| Restart | none |
| Disruption | low |
Rollback
Restore /etc/passwd and /etc/shadow from the backup.
fix
cb_backup /etc/passwd
cb_backup /etc/shadow
awk -F: '$3 < 1000 && $1 !~ /^(root|sync|shutdown|halt)$/ && $7 !~ /^(\/usr)?\/sbin\/nologin$|^\/bin\/false$/ {print $1}' /etc/passwd |
while read -r u; do usermod -s /usr/sbin/nologin "$u"; done
for u in $(awk -F: '$1 != "root" && $7 ~ /nologin$|^\/bin\/false$/ {print $1}' /etc/passwd); do
[ "$(passwd -S "$u" | cut -d' ' -f2)" = L ] || usermod -L "$u"
done
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- usermod(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R34) ANSSI, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| ANSSI-BP-028 - Recommandations de configuration d'un système GNU/Linux2.0 | R34 | equivalent |
| NIST SP 800-53 Rev. 55.1.1 | AC-2 | related |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
| Taxonomy | ID |
|---|---|
| MITRE ATT&CK | T1078.003 |
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. Generated check, fix and check again for profiles core and server-hardened; state kept after reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |