Debian GNU/Linux · Accounts & authentication

Prevent interactive logins to system accounts

When it fails: A system account has a login shell or an unlocked password. Service accounts are rarely watched; one that accepts logins is an easy way in or a quiet persistence point.

CB-DEB-AUTH-018mediumactiveautomatedautomated fixprofile core

As released in v0.1.1 beta · pre-release. Current state.

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

  1. awk -F: '$3 < 1000 && $1 !~ /^(root|sync|shutdown|halt)$/ {print $1, $7}' /etc/passwd: every shell must be /usr/sbin/nologin, /sbin/nologin or /bin/false.
  2. Every account with such a non-login shell must show L with passwd -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

  1. For each listed system account: usermod -s /usr/sbin/nologin <account>.
  2. For each account without a login shell that is not locked: usermod -L <account>.
Restartnone
Disruptionlow

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

Mappings

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

People and lifecycle

Authors
Verified2026-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.
Verified2026-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 inv0.1.0
Created, updated2026-10-18, 2026-10-18
Translationsfr