{
  "id": "CB-UBU-AUTH-014",
  "standard": "ubuntu",
  "version": "0.1.0",
  "url": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-014",
  "json": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-014.json",
  "yaml": "https://baselines.cirsec.org/b/ubuntu/v0.1.0/CB-UBU-AUTH-014.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-014",
    "title": "Set password aging and hashing defaults for new accounts",
    "finding": {
      "title": "New accounts get passwords that never expire and are never disabled when unused",
      "description": "Accounts created with the Ubuntu defaults keep the same password forever and stay usable after their owner left."
    },
    "status": "active",
    "profiles": [
      "core"
    ],
    "risk": {
      "severity": "low"
    },
    "assessment": "automated",
    "tags": [
      "password",
      "accounts",
      "login-defs"
    ],
    "description": "`/etc/login.defs` and `useradd -D` define what new local accounts get:\n\n- `PASS_MAX_DAYS`: maximum age of a password (Ubuntu: 99999, never expires);\n- `PASS_MIN_DAYS`: minimum days between two changes (Ubuntu: 0);\n- `PASS_WARN_AGE`: days of warning before expiry (Ubuntu: 7);\n- `ENCRYPT_METHOD`: hash used by `chpasswd` and `newusers` (Ubuntu 20.04 to 24.04: SHA512,\n  26.04: YESCRYPT; the shadow tools of 20.04 do not support YESCRYPT);\n- `INACTIVE` (`useradd -D`): days after password expiry before the account is disabled (Ubuntu: never).\n",
    "rationale": "A password that is never renewed and an account that is never disabled let a leaked password,\nor a departed user, keep access indefinitely. A minimum age stops users from cycling through\nthe history to get their old password back.\n",
    "impact": "New accounts must change their password every {{ max_days }} days and are disabled\n{{ inactive_days }} days after it expires. Existing accounts are handled by CB-UBU-AUTH-015.\n",
    "notes": "NIST SP 800-63B advises against forced periodic changes when passwords are long and checked\nagainst breach lists. Set `max_days` to 99999 in your tailoring file if you follow that model\nand rely on the quality and lockout controls instead.\n",
    "setting": {
      "location": "/etc/login.defs, /etc/default/useradd",
      "default_value": "PASS_MAX_DAYS 99999, PASS_MIN_DAYS 0, PASS_WARN_AGE 7, INACTIVE -1",
      "recommended_value": "PASS_MAX_DAYS <= {{ max_days }}, PASS_MIN_DAYS >= {{ min_days }}, PASS_WARN_AGE >= 7, YESCRYPT or SHA512, INACTIVE <= {{ inactive_days }}"
    },
    "parameters": [
      {
        "name": "max_days",
        "type": "integer",
        "description": "Maximum password age in days.",
        "unit": "days",
        "default": 365,
        "minimum": 1,
        "maximum": 99999
      },
      {
        "name": "min_days",
        "type": "integer",
        "description": "Minimum days between password changes.",
        "unit": "days",
        "default": 0,
        "profile_defaults": {
          "server-hardened": 1,
          "desktop-hardened": 1
        },
        "minimum": 0,
        "maximum": 7
      },
      {
        "name": "inactive_days",
        "type": "integer",
        "description": "Days after password expiry before the account is disabled.",
        "unit": "days",
        "default": 45,
        "minimum": 0,
        "maximum": 365
      }
    ],
    "audit": {
      "manual": "1. In `/etc/login.defs`: `PASS_MAX_DAYS` <= {{ max_days }}, `PASS_MIN_DAYS` >= {{ min_days }},\n   `PASS_WARN_AGE` >= 7, `ENCRYPT_METHOD` is `YESCRYPT` or `SHA512`.\n2. `useradd -D | grep INACTIVE` shows a value between 0 and {{ inactive_days }}.\n",
      "checks": [
        {
          "id": "login-defs",
          "engine": "shell",
          "description": "Aging and hashing settings in /etc/login.defs.",
          "spec": {
            "script": "get() { sed -n \"s/^[[:space:]]*$1[[:space:]]\\{1,\\}\\([^[:space:]]*\\).*/\\1/p\" /etc/login.defs | tail -n 1; }\nmax=$(get PASS_MAX_DAYS) min=$(get PASS_MIN_DAYS) warn=$(get PASS_WARN_AGE) enc=$(get ENCRYPT_METHOD)\necho \"PASS_MAX_DAYS=$max PASS_MIN_DAYS=$min PASS_WARN_AGE=$warn ENCRYPT_METHOD=$enc\"\n[ \"${max:-99999}\" -le \"$CB_MAX_DAYS\" ] && [ \"${min:-0}\" -ge \"$CB_MIN_DAYS\" ] && [ \"${warn:-0}\" -ge 7 ] \\\n  && { [ \"$enc\" = YESCRYPT ] || [ \"$enc\" = SHA512 ]; }\n"
          }
        },
        {
          "id": "inactive",
          "engine": "shell",
          "description": "Default INACTIVE of useradd.",
          "spec": {
            "script": "i=$(useradd -D | sed -n 's/^INACTIVE=//p')\necho \"INACTIVE=$i\"\n[ \"${i:--1}\" -ge 0 ] && [ \"${i:--1}\" -le \"$CB_INACTIVE_DAYS\" ]\n"
          }
        }
      ]
    },
    "remediation": {
      "manual": "1. Set in `/etc/login.defs`: `PASS_MAX_DAYS {{ max_days }}`, `PASS_MIN_DAYS {{ min_days }}`,\n   `PASS_WARN_AGE 7` and keep `ENCRYPT_METHOD` at `YESCRYPT` (or `SHA512`; only `SHA512` on\n   Ubuntu 20.04).\n2. Run `useradd -D -f {{ inactive_days }}`.\n",
      "effects": {
        "restart": "none",
        "disruption": "none",
        "lockout_risk": false
      },
      "rollback": "Restore `/etc/login.defs` and `/etc/default/useradd` from the backup.\n",
      "scripts": [
        {
          "id": "defaults",
          "engine": "shell",
          "content": "f=/etc/login.defs\ncb_backup \"$f\"\ncb_backup /etc/default/useradd\nset_def() {\n  if grep -Eq \"^[[:space:]]*$1[[:space:]]\" \"$f\"; then sed -i \"s/^[[:space:]]*$1[[:space:]].*/$1\\t$2/\" \"$f\"; else printf '%s\\t%s\\n' \"$1\" \"$2\" >>\"$f\"; fi\n}\nset_def PASS_MAX_DAYS \"$CB_MAX_DAYS\"\nset_def PASS_MIN_DAYS \"$CB_MIN_DAYS\"\nw=$(sed -n 's/^[[:space:]]*PASS_WARN_AGE[[:space:]]\\{1,\\}\\([0-9]*\\).*/\\1/p' \"$f\" | tail -n 1)\n[ \"${w:-0}\" -ge 7 ] || set_def PASS_WARN_AGE 7\ne=$(sed -n 's/^[[:space:]]*ENCRYPT_METHOD[[:space:]]\\{1,\\}\\([A-Z0-9]*\\).*/\\1/p' \"$f\" | tail -n 1)\nh=YESCRYPT\n[ \"$(. /etc/os-release && printf '%s' \"$VERSION_ID\")\" != 20.04 ] || h=SHA512\ncase $e in YESCRYPT | SHA512) ;; *) set_def ENCRYPT_METHOD \"$h\" ;; esac\nuseradd -D -f \"$CB_INACTIVE_DAYS\"\n"
        }
      ]
    },
    "references": [
      {
        "title": "login.defs(5), Ubuntu manual pages",
        "url": "https://manpages.ubuntu.com/manpages/noble/man5/login.defs.5.html",
        "publisher": "Canonical",
        "kind": "vendor-doc",
        "accessed": "2026-10-19"
      },
      {
        "title": "useradd(8), Ubuntu manual pages",
        "url": "https://manpages.ubuntu.com/manpages/noble/man8/useradd.8.html",
        "publisher": "Canonical",
        "kind": "vendor-doc",
        "accessed": "2026-10-19"
      },
      {
        "title": "NIST SP 800-63B - Digital Identity Guidelines, Authentication",
        "url": "https://pages.nist.gov/800-63-4/sp800-63b.html",
        "publisher": "NIST",
        "kind": "standard",
        "accessed": "2026-10-19"
      }
    ],
    "mappings": [
      {
        "framework": "nist-800-53r5",
        "framework_version": "5.1.1",
        "id": "IA-5(1)",
        "relation": "partial"
      },
      {
        "framework": "nist-800-53r5",
        "framework_version": "5.1.1",
        "id": "AC-2(3)",
        "relation": "partial"
      }
    ],
    "threats": [
      {
        "framework": "mitre-attack",
        "id": "T1078.003"
      }
    ],
    "related_controls": [
      {
        "id": "CB-UBU-AUTH-015",
        "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": "Fails on a fresh install as expected; generated fix, check again and second fix idempotent; kept after reboot; profiles core and server-hardened."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "22.04"
        ],
        "environment": "Proxmox VM, Ubuntu 22.04 cloud image, kernel 5.15.0-198-generic",
        "notes": "Fails on a fresh install as expected; generated fix, check again and second fix idempotent; kept after reboot; profiles core and server-hardened."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "24.04"
        ],
        "environment": "Proxmox VM, Ubuntu 24.04 cloud image, kernel 6.8.0-147-generic",
        "notes": "Fails on a fresh install as expected; generated fix, check again and second fix idempotent; kept after reboot; profiles core and server-hardened."
      },
      {
        "date": "2026-10-19",
        "by": "Sn0wAlice",
        "versions": [
          "26.04"
        ],
        "environment": "Proxmox VM, Ubuntu 26.04 cloud image, kernel 7.0.0-38-generic",
        "notes": "Fails on a fresh install as expected; generated fix, check again and second fix idempotent; kept after reboot; profiles core and server-hardened."
      }
    ],
    "introduced_in": "0.1.0",
    "created": "2026-10-19",
    "updated": "2026-10-19"
  }
}