OpenSSH musl Password Bypass, Fixed in 10.6p2 (CVE-2026-123158)
OpenSSH on musl libc without PAM accepts a 257-byte password for locked accounts; fixed in 10.6p2 and Alpine's packages.
TL;DR
- OpenSSH sshd built against musl libc without PAM accepts any password longer than 256 bytes for accounts whose shadow field is
*. The OpenSSH project published CVE-2026-123158 and released 10.6p2 on 2026-11-05. - Alpine Linux servers running sshd with password authentication on are exposed when another local account has a
$5$or$6$hash. Alpine's official images are a large deployment; containers that do not run sshd are not affected. - MITRE scores it CVSS v3.1 8.1 High (CWE-394). A public lab and two proof-of-concept repositories exist; no exploitation in the wild has been reported, and the CVE is not in CISA KEV as of 2026-11-06.
- Upgrade to OpenSSH 10.6p2, or to the fixed Alpine package for your branch, and restart sshd. If you cannot, set
PasswordAuthentication no; see "How do I fix it?".
Am I affected?
You are exposed only when all of these hold:
- The host runs OpenSSH before 10.6p2, or an Alpine openssh package older than the fixed version in the table.
- sshd is built and run against musl libc with PAM disabled. Alpine's
openssh-serveris built without PAM.openssh-server-pamwithUsePAM yesis not affected; the same package withUsePAM nois. PasswordAuthentication yesis in effect. That is the OpenSSH default.- At least one account reachable by password has
*as its shadow password field. On Alpine this includes root, which ships asroot:*in the base image since about Alpine 3.16, key-only users created withadduserand no password, directory-service users that have no shadow entry, and thealpineuser in the official tiny-cloud images. - At least one local account has a
$5$or$6$crypt hash, so sshd can borrow a salt. A machine with no SHA-256 or SHA-512 hash at all does not reach the bug.
For root, the default PermitRootLogin prohibit-password still blocks password logins. Root becomes reachable when PermitRootLogin yes is set, or when another * account that can log in has a route to root, such as passwordless doas on the tiny-cloud images.
Containers that do not run sshd are not affected. Accounts locked with ! cannot log in with a password or a key, so they are not a target. Systems with PasswordAuthentication no, systems using openssh-server-pam with UsePAM yes, and systems with no $-style hash are also outside this bug.
| Branch | Affected versions | Fixed version |
|---|---|---|
| OpenSSH upstream (musl, PAM disabled) | 10.6p1 and earlier | 10.6p2 (released 2026-11-05) |
| Alpine edge | before 10.6_p2-r0 | 10.6_p2-r0 |
| Alpine 3.24 | before 10.3_p1-r2 | 10.3_p1-r2 (point release 3.24.4) |
| Alpine 3.23 | before 10.2_p1-r1 | 10.2_p1-r1 (point release 3.23.8) |
| Alpine 3.22 | before 10.0_p1-r11 | 10.0_p1-r11 (point release 3.22.8) |
| Alpine 3.21 | before 9.9_p2-r1 | 9.9_p2-r1 (point release 3.21.10) |
| Alpine 3.20 | before 9.7_p1-r6 | 9.7_p1-r6 |
| Alpine 3.19 | before 9.6_p1-r3 | 9.6_p1-r3 |
| Alpine 3.18 | before 9.3_p2-r4 | 9.3_p2-r4 |
| Alpine 3.17 | before 9.1_p1-r7 | 9.1_p1-r7 |
| Alpine 3.16 | before 9.0_p1-r6 | 9.0_p1-r6 |
Alpine published fixed packages for every branch from 3.16 through 3.24 and edge, including the releases that no longer receive regular updates. The four current point releases that carry the fix are 3.21.10, 3.22.8, 3.23.8 and 3.24.4.
Check what the host runs:
# Alpine: the installed OpenSSH packages and versions
apk info -v openssh-server openssh-server-pam 2>/dev/null
# Whether this sshd uses PAM (no means the vulnerable build on Alpine)
sshd -T 2>/dev/null | grep -i '^usepam'
# Whether password authentication is on
sshd -T 2>/dev/null | grep -i '^passwordauthentication'
# Accounts whose shadow password field is exactly *
sudo awk -F: '$2=="*"{print $1}' /etc/shadow
# Whether any local account has a SHA-256 or SHA-512 hash
sudo awk -F: '$2 ~ /^\$[56]\$/{print $1}' /etc/shadow
sshd -T needs root and a readable configuration. On a musl system that is not Alpine, such as OpenWrt, Buildroot or Yocto, use the distribution's package manager and ssh -V to identify the OpenSSH build, then compare it with the vendor's fixed package.
How do I fix it?
- On Alpine, upgrade the package and restart sshd:
apk upgrade -U -a rc-service sshd restartUpgrading does not restart a running sshd, so the restart is required. Rebuild any container image from the fixed base or package and recreate the container; pulling a new image without recreating the container leaves the old process running. - On another musl system, upgrade OpenSSH to 10.6p2 or to the fixed package from the distribution. The OpenSSH 10.6p2 release notes name the fix, and Alpine's advisory lists the fixed package for each branch.
- If you cannot upgrade, turn password authentication off. Keep your current session open and test a key login before you close it. On Alpine 3.19 and later, the drop-in file is enough:
mkdir -p /etc/ssh/sshd_config.d echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-passwords.conf sshd -t && rc-service sshd restart sshd -T | grep -i passwordauthenticationOn older Alpine releases, which ignore the drop-in directory, edit/etc/ssh/sshd_configdirectly and restart sshd. SettingPasswordAuthentication noalso removes password logins for accounts with real hashes; that is the intended cost. - If you need password authentication and cannot upgrade, remove the
*targets or make them unreachable. Lock unused accounts with a!field instead of*, or remove them. A!field also blocks key logins; keep an alternate administrative session open. This closes the specific account path but does not fix the comparison, so upgrade as soon as you can.
What is the root cause?
The bug is a comparison between two strings that both read *, one because the account has no password and one because musl's crypt() uses * as its error value. In auth-passwd.c, sshd without PAM takes the stored password field and hashes the supplied password with it:
char *pw_password = authctxt->valid ? shadow_pw(pw) : pw->pw_passwd;
if (pw_password == NULL)
return 0;
/* Check for users with no password. */
if (strcmp(pw_password, "") == 0 && strcmp(password, "") == 0)
return (1);
/*
* Encrypt the candidate password using the proper salt, or pass a
* NULL and let xcrypt pick one.
*/
if (authctxt->valid && pw_password[0] && pw_password[1])
salt = pw_password;
encrypted_password = xcrypt(password, salt);
/*
* Authentication is accepted if the encrypted passwords
* are identical.
*/
return encrypted_password != NULL &&
strcmp(encrypted_password, pw_password) == 0;
For an account whose field is *, pw_password[1] is the terminating NUL, so salt stays NULL. xcrypt() then calls pick_salt(), which walks the account database until it finds a $5$ or $6$ field and returns that hash's type and salt:
if (salt == NULL)
salt = pick_salt();
...
crypted = crypt(password, salt);
musl's SHA-512 implementation rejects a key longer than 256 bytes. The limit is a denial-of-service guard, because SHA-512 crypt slows down as the key grows:
#define KEY_MAX 256
...
/* reject large keys */
for (i = 0; i <= KEY_MAX && key[i]; i++);
if (i > KEY_MAX)
return 0;
When the inner function returns 0, musl's __crypt_sha512() returns the string *:
p = sha512crypt(key, setting, output);
...
if (!p || q != testbuf || memcmp(testbuf, testhash, sizeof testhash))
return "*";
return p;
sshd compares the returned * with the stored * and accepts the login. OpenSSH caps the supplied password at 1024 bytes, so the successful range is 257 to 1024 bytes. The CVE record describes it as 256 to 1024; the 256-byte key is still accepted by musl and produces a real hash, so 257 is the shortest value that triggers the error return.
The bug became reachable in 2016, when OpenSSH added pick_salt() as a timing side-channel fix for CVE-2016-6210. Before that change, a * account was hashed with the fixed DES salt xx, which did not return *.
How is it exploited?
- The attacker finds an SSH service on a musl system that has password authentication enabled.
- The target has a
*account that can log in by password: root withPermitRootLogin yes, a key-only deploy user, a directory user, or thealpineuser on a tiny-cloud image. - Another local account has a
$5$or$6$hash, so sshd can borrow its salt. - The attacker sends a password of 257 to 1024 bytes for the
*account. The characters do not matter. - sshd hashes the long password with the borrowed salt. musl returns
*; the comparison with the stored*succeeds, and the attacker gets a shell as that account.
The public lab at sshhole.com lets a reader try the comparison in a browser, and the CVE record lists two proof-of-concept repositories. No exploitation in the wild has been reported publicly as of 2026-11-06.
How does the attack flow?
A long password for an account with no password reaches musl's * error return, and sshd accepts it because the stored field is also *.
What can an attacker do?
An attacker can log in as any * account that password authentication reaches, with the privileges of that account. The impact depends on which account it is:
- Root on Alpine, when
PermitRootLogin yesis set: full control of the host. - The
alpineuser on a tiny-cloud image, which has passwordlessdoas: root on the instance. - A key-only deploy user: the attacker gets the shell that user would have had, including its keys, cloud credentials and access to the application.
- A directory-service user with no shadow entry: the attacker impersonates that user.
Every condition from "Am I affected?" applies: musl, PAM disabled, password authentication on, a * account, a $5$ or $6$ hash elsewhere, and network reachability. The bug does not reveal or crack a real password hash, and it does not affect accounts locked with !. It is not a pre-authentication bug in the sense of bypassing the SSH protocol; the attacker still completes a normal password login, with a password the server should have rejected.
Why does the fix work?
The fix adds a minimum length for a stored hash and refuses to compare a one-character field:
+#define MIN_PASSWORD_HASH_LEN 4
#define MAX_PASSWORD_LEN 1024
@@
- return encrypted_password != NULL &&
+ ret = encrypted_password != NULL &&
+ strlen(pw_password) >= MIN_PASSWORD_HASH_LEN &&
strcmp(encrypted_password, pw_password) == 0;
+ freezero(pw_password, strlen(pw_password));
+ return ret;
A * field is one byte, shorter than the new four-byte minimum, so the comparison is false even when musl returns *. A real SHA-512 field is far longer than four bytes: $6$, a salt and the hash. The same change is applied to the portable and UnixWare password paths, and the fix also frees the shadow copy. The OpenSSH project calls this a workaround because the root cause is musl's error value, but it closes the sshd path. Alpine's stable packages carry the same patch.
How do I check if I was compromised?
There are no published indicators of compromise. A successful login leaves an Accepted password line for an account that should never accept a password, so start there:
# Alpine and other syslog-based systems
sudo grep -i "Accepted password" /var/log/messages
# Compare the account name with the * accounts on the host
sudo awk -F: '$2=="*"{print $1}' /etc/shadow
A hit is a lead, not proof: an account with a real hash can also produce an Accepted password line. Check the source address and the time against your own logins. Then review doas and sudo use, recently created users, new authorized_keys entries, and any files or services the account could reach. On a tiny-cloud image, check whether the alpine user logged in from an address you do not recognise.
A failed attempt leaves no special trace, and a successful one may have been deleted from the logs. If you cannot rule out a login, treat the host as compromised, rebuild it from a trusted image and rotate every credential it could reach.
Timeline
- 2012-08: musl changes
crypt()to return*instead ofNULLwhen it cannot compute a hash. - 2012-08-29: musl caps SHA-512 crypt keys at 256 bytes, which makes the long-key path return
*. - 2016-07: OpenSSH adds
pick_salt()for invalid users as the CVE-2016-6210 timing fix, which makes the*comparison reachable. - 2026-10-30: an OpenSSH security review fixes
pick_salt(); the vulnerable comparison is four lines after it. - 2026-11-05: OpenSSH 10.6p2 is released, CVE-2026-123158 is published, and Alpine publishes its advisory and fixed packages.
- 2026-11-06: no exploitation in the wild has been reported publicly; the CVE is not in CISA KEV.
FAQ
Does this affect Debian, Ubuntu or Fedora?
No. Those systems use glibc and PAM, and the bypass needs musl's * error return with PAM disabled. The same OpenSSH version on glibc does not take this path.
I use Alpine but only key-based logins. Am I affected?
Yes, if PasswordAuthentication is still on and a * account exists. The attacker logs in as the * account with a long password; your own key-only login method does not matter. Set PasswordAuthentication no or upgrade.
Is root exploitable by default?
Not by default. Alpine ships root with a * field, but OpenSSH's default PermitRootLogin prohibit-password blocks password logins for root. Root becomes reachable when PermitRootLogin yes is set, or when another * account can escalate, such as the alpine user with passwordless doas.
Is there a CVE I can track?
Yes. CVE-2026-123158 was published on 2026-11-05 with CVSS v3.1 8.1 High and CWE-394. The OpenSSH advisory and the Alpine advisory are the primary remediation sources.
Was this exploited in the wild?
No public report of exploitation exists as of 2026-11-06, and CISA's SSVC assessment records a public proof of concept but not active exploitation. The bug is old enough that a quiet login cannot be ruled out from public data, so check the logs.
Do I need to rotate SSH keys?
Only if you find evidence of a login you cannot explain. If the host was accessed, treat every key and credential it could reach as exposed and rotate them after rebuilding. If the logs are clean, upgrading and restarting sshd is enough.
Find exposed SSH services with Ostorlab
Ostorlab's attack surface discovery inventories your internet-facing hosts and the services they expose. Use it to list the machines that answer on TCP 22, then check their OpenSSH build and password-authentication setting first.
References
- OpenSSH 10.6p2 release notes, 2026-11-05, the password-authentication bypass and the credit to Özgün Kültekin: https://www.openssh.org/releasenotes.html#10.6p2 (checked November 6, 2026)
- OpenSSH commit 4353fdc43ef646c43a8dda7025cc1a898caf1482, "avoid password authn bypass", 2026-11-05, the
MIN_PASSWORD_HASH_LENcheck: https://github.com/openssh/openssh-portable/commit/4353fdc43ef646c43a8dda7025cc1a898caf1482 (checked November 6, 2026) - CVE-2026-123158 record, MITRE, published 2026-11-05, CVSS v3.1 8.1, CWE-394 and the proof-of-concept references: https://www.cve.org/CVERecord?id=CVE-2026-123158 (checked November 6, 2026)
- Alpine Linux, "OpenSSH password authentication bypass", 2026-11-05, the affected configurations, fixed package versions and workaround: https://alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/ (checked November 6, 2026)
- "sshole", the researcher's write-up and browser lab, the musl and OpenSSH history and the exploitation conditions: https://sshhole.com/ (checked November 6, 2026)
- musl commit b3c4cc12, "crypt: return "*" instead of NULL on failure", August 2012: https://git.musl-libc.org/cgit/musl/commit/?id=b3c4cc12 (checked November 6, 2026)
- musl commit 88bf5a8a, the 256-byte SHA-512 key cap, 2012-08-29: https://git.musl-libc.org/cgit/musl/commit/?id=88bf5a8a (checked November 6, 2026)
- OpenSSH
auth-passwd.cat the 10.6p1 tag, the vulnerable comparison: https://github.com/openssh/openssh-portable/blob/V_10_6_P1/auth-passwd.c (checked November 6, 2026) - OpenSSH
openbsd-compat/xcrypt.cat the 10.6p1 tag,pick_salt()andxcrypt(): https://github.com/openssh/openssh-portable/blob/V_10_6_P1/openbsd-compat/xcrypt.c (checked November 6, 2026) - Docker Hub, the official
alpineimage, 12,476,923,091 pulls, the reach figure: https://hub.docker.com/_/alpine (checked November 6, 2026) - CISA Known Exploited Vulnerabilities catalog JSON feed, checked for CVE-2026-123158 on November 6, 2026 (not listed): https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json (checked November 6, 2026)