← All playbooks
HighPatch · 2026-11-05

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.

MITRE ATT&CK: T1133, T1078

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