{
  "id": "CB-UBU-AUTH-013",
  "standard": "ubuntu",
  "version": "0.1.0",
  "url": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-013",
  "json": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-013.json",
  "yaml": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-013.yml",
  "profiles_effective": [
    "core",
    "server",
    "server-hardened",
    "desktop",
    "desktop-hardened"
  ],
  "license": "Text CC BY 4.0, code Apache-2.0",
  "attribution": "CIRSEC Baselines, https://baselines.cirsec.org",
  "control": {
    "id": "CB-UBU-AUTH-013",
    "title": "Refuse empty passwords and hash passwords with a strong algorithm in pam_unix",
    "finding": {
      "title": "pam_unix accepts accounts with an empty password",
      "description": "An account whose password field is empty can be logged into without any password."
    },
    "status": "active",
    "profiles": [
      "core"
    ],
    "risk": {
      "severity": "high"
    },
    "assessment": "automated",
    "tags": [
      "pam",
      "password"
    ],
    "description": "`pam_unix` checks local passwords against `/etc/shadow`. Three options matter here:\n\n- `nullok` lets accounts with an empty password field authenticate with no password. Ubuntu\n  sets it in the default `unix` profile;\n- the hashing method used when a password is changed: `yescrypt` from Ubuntu 22.04, `sha512`\n  on 20.04, whose `pam_unix` (PAM 1.3.1) does not support yescrypt. Older methods (`md5`,\n  `bigcrypt`, DES) are fast to crack;\n- `use_authtok` in the password stack: `pam_unix` stores the new password that the modules\n  before it (quality check, history) accepted, instead of asking for it again unchecked.\n",
    "rationale": "An empty password field, created by mistake or by a tool, becomes an open account as long as\n`nullok` is present. A weak hashing method lets an attacker who copies `/etc/shadow` recover\npasswords quickly.\n",
    "impact": "Accounts with an empty password can no longer log in with a password. Give them a password, or\nlock them with `passwd -l`.\n",
    "notes": "The packaged `unix` profile is replaced by a copy without `nullok`, so package upgrades do not\nbring the option back. Passwords keep their current hash until they are changed.\n\n`pam-auth-update` writes `use_authtok` on the `pam_unix.so` password line only when another\npassword module runs before it. On a default system `pam_unix` is first and the option is\nabsent: it appears once `pam_pwquality` (CB-UBU-AUTH-010) or `pam_pwhistory`\n(CB-UBU-AUTH-012) is enabled. Adding it by hand while `pam_unix` is first would break password\nchanges.\n",
    "setting": {
      "location": "/usr/share/pam-configs/cirsec_unix",
      "default_value": "nullok; yescrypt (sha512 on 20.04); no use_authtok",
      "recommended_value": "No nullok, yescrypt or sha512, use_authtok"
    },
    "audit": {
      "manual": "1. No `pam_unix.so` line in `/etc/pam.d/common-auth` or `/etc/pam.d/common-password` contains `nullok`.\n2. The `pam_unix.so` line of `/etc/pam.d/common-password` contains `yescrypt` or `sha512`,\n   and `use_authtok`.\n",
      "checks": [
        {
          "id": "no-nullok-auth",
          "engine": "file-content",
          "spec": {
            "path": "/etc/pam.d/common-auth",
            "pattern": "pam_unix\\.so.*\\bnullok\\b",
            "expect": "no_match"
          }
        },
        {
          "id": "no-nullok-password",
          "engine": "file-content",
          "spec": {
            "path": "/etc/pam.d/common-password",
            "pattern": "pam_unix\\.so.*\\bnullok\\b",
            "expect": "no_match"
          }
        },
        {
          "id": "strong-hash",
          "engine": "file-content",
          "spec": {
            "path": "/etc/pam.d/common-password",
            "pattern": "^\\s*password\\s+.*pam_unix\\.so.*\\b(yescrypt|sha512)\\b"
          }
        },
        {
          "id": "use-authtok",
          "engine": "file-content",
          "description": "pam_unix stores the password accepted by the modules before it.",
          "spec": {
            "path": "/etc/pam.d/common-password",
            "pattern": "^\\s*password\\s+.*pam_unix\\.so.*\\buse_authtok\\b"
          }
        }
      ]
    },
    "remediation": {
      "manual": "1. Lock or set a password for every account with an empty field: `awk -F: '$2==\"\"' /etc/shadow`.\n2. Copy `/usr/share/pam-configs/unix` to `/usr/share/pam-configs/cirsec_unix`, remove every\n   `nullok`, change `Name:` and make sure the `Password:` lines carry `yescrypt` (`sha512` on\n   20.04).\n3. Run `pam-auth-update --enable cirsec_unix --disable unix`. On Ubuntu 20.04 and 22.04,\n   whose `pam-auth-update` has no `--disable`, run\n   `pam-auth-update --enable cirsec_unix --remove unix`, then add a `unix` line to\n   `/var/lib/pam/seen` so that later runs do not enable the profile again.\n4. Enable a password quality or history module (CB-UBU-AUTH-010, CB-UBU-AUTH-012) so that\n   `use_authtok` is written on the `pam_unix.so` password line.\n5. Check that a new login still works before closing your session.\n",
      "effects": {
        "restart": "none",
        "disruption": "low",
        "lockout_risk": true
      },
      "rollback": "Run `pam-auth-update --enable unix --remove cirsec_unix` and delete the profile file.\n",
      "scripts": [
        {
          "id": "profile",
          "engine": "shell",
          "content": "p=/usr/share/pam-configs/cirsec_unix\nsed -e '/^Name:/!s/[[:space:]]nullok\\b//g' \\\n    -e 's/^Name: .*/Name: CIRSEC - Unix authentication without nullok/' /usr/share/pam-configs/unix >\"$p\"\nh=yescrypt\n[ \"$(. /etc/os-release && printf '%s' \"$VERSION_ID\")\" != 20.04 ] || h=sha512\ngrep -q yescrypt \"$p\" || grep -q sha512 \"$p\" || sed -i \"s/\\(pam_unix\\.so obscure\\)/\\1 $h/\" \"$p\"\nfor f in /etc/pam.d/common-auth /etc/pam.d/common-password /etc/pam.d/common-account /etc/pam.d/common-session; do cb_backup \"$f\"; done\nif grep -q -- '--disable' /usr/sbin/pam-auth-update; then\n  DEBIAN_FRONTEND=noninteractive pam-auth-update --enable cirsec_unix --disable unix\nelse\n  # Ubuntu 20.04 and 22.04: no --disable. Remove the profile, then mark it as seen so later\n  # runs (package upgrades, other profiles) do not enable it again as a new default.\n  DEBIAN_FRONTEND=noninteractive pam-auth-update --enable cirsec_unix --remove unix\n  grep -qx unix /var/lib/pam/seen || echo unix >>/var/lib/pam/seen\nfi\n"
        }
      ]
    },
    "references": [
      {
        "title": "pam_unix(8), Ubuntu manual pages",
        "url": "https://manpages.ubuntu.com/manpages/noble/man8/pam_unix.8.html",
        "publisher": "Canonical",
        "kind": "vendor-doc",
        "accessed": "2026-10-19"
      },
      {
        "title": "pam-auth-update(8), Ubuntu manual pages",
        "url": "https://manpages.ubuntu.com/manpages/noble/man8/pam-auth-update.8.html",
        "publisher": "Canonical",
        "kind": "vendor-doc",
        "accessed": "2026-10-19"
      },
      {
        "title": "ANSSI-BP-028 v2.0 - Recommandations de configuration d'un système GNU/Linux (R68)",
        "url": "https://messervices.cyber.gouv.fr/documents-guides/fr_np_linux_configuration-v2.0.pdf",
        "publisher": "ANSSI",
        "kind": "guide",
        "accessed": "2026-10-19"
      }
    ],
    "mappings": [
      {
        "framework": "anssi-linux",
        "framework_version": "2.0",
        "id": "R68",
        "relation": "partial",
        "note": "R68 is about protecting stored passwords; this control covers the hashing method."
      },
      {
        "framework": "nist-800-53r5",
        "framework_version": "5.1.1",
        "id": "IA-5(1)",
        "relation": "partial"
      }
    ],
    "threats": [
      {
        "framework": "mitre-attack",
        "id": "T1078.003"
      },
      {
        "framework": "cwe",
        "id": "CWE-916"
      }
    ],
    "related_controls": [
      {
        "id": "CB-UBU-AUTH-010",
        "type": "related"
      },
      {
        "id": "CB-UBU-AUTH-012",
        "type": "related"
      }
    ],
    "authors": [
      {
        "name": "Alice",
        "github": "Sn0wAlice"
      }
    ],
    "verified": [
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "20.04"
        ],
        "environment": "Proxmox VM, Ubuntu 20.04 cloud image, kernel 5.4.0-216-generic",
        "notes": "nullok removed through the cirsec_unix profile (--remove unix and seen entry, no --disable on this release), sha512 kept, use_authtok present; password login works with pamtester (login, sshd, sudo); unchanged after dpkg-reconfigure libpam-runtime and reboot."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "22.04"
        ],
        "environment": "Proxmox VM, Ubuntu 22.04 cloud image, kernel 5.15.0-198-generic",
        "notes": "nullok removed through the cirsec_unix profile (--remove unix and seen entry, no --disable on this release), use_authtok present; password login works with pamtester (login, sshd, sudo); unchanged after dpkg-reconfigure libpam-runtime and reboot."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "24.04"
        ],
        "environment": "Proxmox VM, Ubuntu 24.04 cloud image, kernel 6.8.0-147-generic",
        "notes": "nullok removed through the cirsec_unix profile (--disable unix), use_authtok present; password login works with pamtester (login, sshd, sudo); unchanged after dpkg-reconfigure libpam-runtime and reboot."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "26.04"
        ],
        "environment": "Proxmox VM, Ubuntu 26.04 cloud image, kernel 7.0.0-38-generic",
        "notes": "nullok removed through the cirsec_unix profile (--disable unix), use_authtok present; password login works with pamtester (login, sshd, sudo); unchanged after dpkg-reconfigure libpam-runtime and reboot."
      }
    ],
    "introduced_in": "0.1.0",
    "created": "2026-10-19",
    "updated": "2026-10-19"
  }
}