OpenSSH / Alpine “sshole” CVE-2026-123158: key-only account password-auth bypass
What happened
On musl-based Linux with PAM disabled, sshd can accept a 257–1024 byte password for an existing account whose shadow password field is *. musl crypt() returns * for an oversize password; the error value can match the stored field. At least one other local account with a $5$ or $6$ password hash must exist. Alpine says one reachable SSH connection is enough, with no retries. OpenSSH shipped 10.6p2 on November 5 and calls this a critical security workaround; the CVE is reported as CVSS 3.1 8.1 (High) by Cycode. Alpine’s original advisory said a CVE had not yet been assigned; Cycode now identifies CVE-2026-123158.
Who's affected
Alpine 3.16 through edge using openssh-server (built without PAM), or openssh-server-pam with UsePAM no, when PasswordAuthentication yes, a reachable account has * in its password field, and another local account has a $5$/$6$ hash. Other musl-based OpenSSH builds require review under the same conditions; do not assume all devices or images are vulnerable. Official tiny-cloud images 3.18.11–3.24.3 have an alpine user with passwordless doas: safe on first boot, but exposed once the required hashed-password account exists. Default PermitRootLogin prohibit-password protects root directly, not a doas-capable user. Not affected: glibc distributions such as Debian/Ubuntu/Fedora, PasswordAuthentication no, active PAM authentication (UsePAM yes), accounts locked with !, or containers without sshd. Alpine cloud-init images that disable password authentication are not affected.
✅ Fix / patch
- Patch exposed Alpine SSH hosts now: run apk upgrade -U -a, then rc-service sshd restart. Upgrading the package alone does NOT restart the running daemon. For images, rebuild and redeploy from patched packages; make sure the running sshd is replaced.
- Fixed Alpine openssh packages (same branch, or a later vendor-fixed revision): edge 10.6_p2-r0; 3.24 10.3_p1-r2; 3.23 10.2_p1-r1; 3.22 10.0_p1-r11; 3.21 9.9_p2-r1; 3.20 9.7_p1-r6; 3.19 9.6_p1-r3; 3.18 9.3_p2-r4; 3.17 9.1_p1-r7; 3.16 9.0_p1-r6. These are backports: an older upstream version number can still be fixed. Self-built musl OpenSSH: update to upstream 10.6p2 or the distribution’s patched build.
- If patching must wait: first confirm key login works and keep the current session open. Set PasswordAuthentication no in the effective sshd configuration, including applicable Match blocks. Run sshd -t, restart sshd, and verify a fresh key login and sshd -T output. Consult Alpine’s advisory for its full configuration procedure, especially before 3.19, where default drop-in support differs.
- Verify openssh package revisions with apk info -v openssh-server openssh-server-pam and confirm that the daemon was restarted. Older branches received an emergency fix, not renewed general support: plan migration to a supported Alpine release.
- If an unexplained Accepted password event targets a key-only account, preserve logs, isolate the host and investigate as a possible compromise. Review doas activity, new users and authorized_keys changes; a patch does not undo prior access.
🔎 Quick hunt check
Inventory Alpine/musl hosts and images actually running sshd. Locally check sshd -T | grep -i -E 'passwordauthentication|usepam|permitrootlogin' and apk info -v openssh-server openssh-server-pam; evaluate applicable Match rules for each user/source. An absent UsePAM output on a non-PAM build does not mean PAM is protecting it. Review account password-field state locally without exporting password hashes. Search /var/log/messages and forwarded auth logs for Accepted password for accounts expected to be key-only, especially alpine and doas-capable users. A successful bypass need not have preceding failures; ordinary sshd logs do not record password length, so these queries identify leads, not proof of exploitation. Query setup: create an asset inventory CSV alpine_musl_hosts.csv in Splunk, and a Sentinel watchlist AlpineMuslSSHHosts, containing only confirmed Alpine/musl SSH hosts. Required CSV columns: host,distro,openssh_version,passwordauthentication,usepam (unknown values may be blank); watchlist SearchKey must match Syslog.Computer, with the other four columns using the same names. Populate version/configuration fields from your management inventory or approved local checks, not from auth logs. The queries show accepted-password events alongside that inventory; validate each account’s expected key-only status and historical exposure. Do not filter out historical logins merely because the current host is now patched.
IOCs
No IOCs (IPs, domains or hashes) published by the sources yet.
No IP, domain or file-hash indicators were published by the cited sources. Hunt accepted-password logins on key-only accounts; this is a behavioral check, not a static IOC signature.
Hunt queries
Starting points built only from published indicators. Field names vary; tune before use.
index=os earliest=-7d (sourcetype=linux_secure OR sourcetype=syslog) "Accepted password for"
| rex field=_raw "Accepted password for (?<ssh_user>\S+) from (?<src_ip>\S+)"
| lookup alpine_musl_hosts.csv host OUTPUT distro openssh_version passwordauthentication usepam
| where isnotnull(distro) AND isnotnull(ssh_user)
| stats count min(_time) as first_seen max(_time) as last_seen values(distro) as distro values(openssh_version) as openssh_version values(passwordauthentication) as PasswordAuthentication values(usepam) as UsePAM by host ssh_user src_ip
| convert ctime(first_seen) ctime(last_seen)
| eval triage="Verify key-only/* account status and doas activity; version/config are inventory, not historical proof"// Requires the curated AlpineMuslSSHHosts watchlist described in Hunt check.
let HostInventory = _GetWatchlist("AlpineMuslSSHHosts")
| project Computer=tostring(SearchKey), Distro=tostring(distro),
OpenSSHVersion=tostring(openssh_version),
PasswordAuthentication=tostring(passwordauthentication), UsePAM=tostring(usepam);
Syslog
| where TimeGenerated >= ago(7d)
| where ProcessName startswith "sshd"
| where SyslogMessage contains "Accepted password for "
| extend SSHUser=extract(@"Accepted password for (\S+) from", 1, SyslogMessage),
SourceIP=extract(@" from (\S+) port ", 1, SyslogMessage)
| where isnotempty(SSHUser)
| join kind=inner HostInventory on Computer
| summarize Logins=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated)
by Computer, SSHUser, SourceIP, Distro, OpenSSHVersion, PasswordAuthentication, UsePAM
// Triage against key-only/* account status and doas activity.
// Current inventory does not establish the host's configuration at login time.⏱ CISA deadline
Not in CISA KEV as of Nov 5, 2026.
Sources
Related playbooks
- Linux kernel CVE-2026-98432: no-login remote root via MPTCP listeners · Patch · 2026-10-30
- Chainstack breached: customer data and password hashes accessed, node credentials treated as exposed · Breach · 2026-11-04
- Attackers drain BTCPay Server hot wallets on version 2.4.1 and older · Exploit · 2026-11-03
SockAnalyst