Skip to content

auth: classify $p$ lookups by account state#25

Open
hauke wants to merge 1 commit into
openwrt:masterfrom
hauke:system-password
Open

auth: classify $p$ lookups by account state#25
hauke wants to merge 1 commit into
openwrt:masterfrom
hauke:system-password

Conversation

@hauke
Copy link
Copy Markdown
Member

@hauke hauke commented May 20, 2026

uh_auth_add resolves '$p$' password lines by reading the named account's stored credential from /etc/shadow (or /etc/passwd as a fallback). The previous code copied that string verbatim into realm->pass without checking whether it was a usable crypt(3) hash. This broke for two distinct shadow states:

  1. Locked or placeholder credentials ("", "x", "!", "!!", "LK", "NP", "!hash" lock prefix). In uh_auth_check the plaintext compare branch fires whenever realm->pass does not start with '$', so any client sending the placeholder verbatim as the password matched and authenticated. On OpenWrt every system account except root has '' or 'x' in /etc/shadow by default, so a realm configured as '/cgi-bin/admin:adminuser:$p$daemon' (the pattern the example UCI in package/network/services/uhttpd/files/uhttpd.config documents) authenticated any request with password '*' and ran the CGI as root.

  2. Empty password (the default OpenWrt root entry, "root:::..."): handled correctly by the existing empty-pass drop but silently, leaving admins unaware that '$p$root' on a fresh image produces a public URL.

Match login(1) semantics: locked or placeholder accounts deny all access, accounts with no password set permit access without authentication, accounts with a real hash require it. For each non-hash case log a distinct warning so admins notice the silent state. A '$p$' reference to a non-existent account is treated as a config typo: deny all access (consistent with the admin's stated intent of requiring auth) and log a warning.

For the locked case, bind the realm to a sentinel that starts with '$' (skipping the plaintext compare branch) and cannot be produced by crypt(3) (failing the hash branch), so every request returns 401 instead of falling through to a missing-realm public response.

Reported-by: Amit Pinchasi amitpinchasi123@gmail.com

uh_auth_add resolves '$p$<user>' password lines by reading the named
account's stored credential from /etc/shadow (or /etc/passwd as a
fallback). The previous code copied that string verbatim into
realm->pass without checking whether it was a usable crypt(3) hash.
This broke for two distinct shadow states:

 1. Locked or placeholder credentials ("*", "x", "!", "!!", "*LK*",
    "*NP*", "!hash" lock prefix). In uh_auth_check the plaintext
    compare branch fires whenever realm->pass does not start with '$',
    so any client sending the placeholder verbatim as the password
    matched and authenticated. On OpenWrt every system account except
    root has '*' or 'x' in /etc/shadow by default, so a realm configured as
    '/cgi-bin/admin:adminuser:$p$daemon' (the pattern the example UCI
    in package/network/services/uhttpd/files/uhttpd.config documents)
    authenticated any request with password '*' and ran the CGI as
    root.

 2. Empty password (the default OpenWrt root entry, "root:::..."):
    handled correctly by the existing empty-pass drop but silently,
    leaving admins unaware that '$p$root' on a fresh image produces a
    public URL.

Match login(1) semantics: locked or placeholder accounts deny all
access, accounts with no password set permit access without
authentication, accounts with a real hash require it. For each
non-hash case log a distinct warning so admins notice the silent
state. A '$p$' reference to a non-existent account is treated as a
config typo: deny all access (consistent with the admin's stated
intent of requiring auth) and log a warning.

For the locked case, bind the realm to a sentinel that starts with
'$' (skipping the plaintext compare branch) and cannot be produced by
crypt(3) (failing the hash branch), so every request returns 401
instead of falling through to a missing-realm public response.

Reported-by: Amit Pinchasi <amitpinchasi123@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Link: openwrt#25
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
@hauke hauke force-pushed the system-password branch from d43fa9e to 1b624f8 Compare May 20, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant