A newly disclosed OpenSSH flaw, CVE-2026-123158 or 'sshhole', lets attackers log into locked accounts on Alpine Linux and other musl-based systems by typing any password longer than 256 characters. Public exploits are already circulating, and fixes shipped within a day of the report.

On Alpine Linux and other systems that pair OpenSSH with the musl C library and skip PAM (Pluggable Authentication Modules, the standard Linux login framework), some accounts that administrators believe are locked against password login will accept any password longer than 256 characters. The bug, tracked as CVE-2026-123158 and nicknamed "sshhole" by the researcher who found it, lets a remote, unauthenticated attacker log in without knowing a single real credential. In some common cloud configurations, that leads straight to root. Public proof-of-concept code, reproduction labs and a Google scanner module were all published on November 5, 2026, the same day OpenSSH shipped an emergency fix.
On November 5, 2026, the OpenSSH project released OpenSSH 10.6p2, which its release notes describe as containing a critical security workaround for a password authentication bypass in sshd, the SSH server program. MITRE published CVE-2026-123158 that morning (15:46 UTC). The CVE record carries a CVSS 3.1 base score of 8.1 (High) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. By 17:12 UTC, CISA's ADP Vulnrichment team had added an SSVC assessment:
poc (a public proof-of-concept exists)nototalCISA tagged the researcher's write-up site, sshhole.com, as an exploit reference.
OpenSSH credits Özgün Kültekin of Wakefield (wakefield.co) with reporting the bug. According to the advisory posted to the openssh-unix-dev mailing list, the OpenSSH developers learned of the issue only the day before release, on November 4, 2026. They chose to ship immediately rather than hold the fix under a long embargo, meaning a period of confidential coordination before public disclosure. The team said it now considers long embargoes dangerous because it has recently seen multiple cases of bugs being rediscovered by AI agents, sometimes within hours. The advisory also thanked Alpine Linux developers for producing updated packages on very short notice.
OpenSSH developer Damien Miller stressed that the project does not consider the root cause to lie within OpenSSH itself. It sits in how the musl C library behaves when password hashing fails. But OpenSSH is "well equipped to avoid it," so the project shipped the workaround. Miller also widened the affected scope beyond the CVE text. OpenSSH builds compiled with PAM support (--with-pam) are also vulnerable if the running configuration sets UsePAM no. He confirmed that Dropbear SSH, a lightweight SSH server popular on embedded and Alpine systems, was analyzed and is not affected.
The researcher's account on sshhole.com describes how the flaw was found. Kültekin built sshd every way they could think of (glibc, musl, PAM, no PAM, shadow, no shadow) and ran roughly 17,600 unusual login attempts against each build using automated agent swarms. The test cases included odd hashes, broken accounts and absurdly long passwords. In their words: "every build agreed on every login. except musl without pam. that one said yes to things it shouldn't."
This bug has no memory corruption and no clever shellcode. It comes from three reasonable decisions, made years apart by different projects, that happen to line up.
On Unix-like systems, an account's password hash lives in /etc/shadow. A hash is a one-way scrambled version of the password. When the password field holds a value that can never be a real hash, such as a single asterisk (*), password login is disabled for that account. Depending on configuration, the user can still log in with an SSH key. According to sshhole.com, this convention goes back to 1980, when Bill Joy used * for the daemon account in 3BSD's /etc/passwd.
Alpine Linux leans on this convention:
!.! also blocked SSH key logins.*, which kept the root password locked but made key logins work again.The Alpine advisory adds that accounts such as the default alpine user in Alpine's tiny-cloud images, key-only users, and users retrieved through musl-nscd (a service that lets musl systems look up users from external directories) can also carry a * password field.
crypt() answers failure with "*"crypt() is the standard C function that turns a password plus a "salt" into a hash. The salt is a random value stored with the hash, such as $6$abc123..., where $5$ means SHA-256 and $6$ means SHA-512. Traditionally, crypt() returns NULL when it fails, and many programs crashed because they never checked for that.
According to sshhole.com:
b3c4cc12: musl changed crypt() to return the string "*" on failure instead of NULL, so careless callers would no longer crash.88bf5a8a: musl added SHA-512 crypt with a 256-byte cap on password length. SHA-512 crypt gets slower as the password gets longer, so an attacker could tie up a server with one giant password. Inputs longer than 256 bytes are rejected.The researcher summed up the combination: "so past 256 bytes crypt fails. and when crypt fails... it says ""."*
glibc systems that use libxcrypt avoid the trap because libxcrypt signals failure with NULL or *0, which can never equal a lone *. sshhole.com notes that this was a near miss. In 2013, Debian bug #714219 considered copying musl's * behavior into glibc before Debian moved to libxcrypt. In December 2024, a thread on the musl mailing list noted that BusyBox passwd failed on 257-character passwords for exactly this reason (the 256-byte limit plus the * return value), but nobody connected it to sshd.
In 2016, OpenSSH fixed CVE-2016-6210, a timing attack that let attackers tell which usernames existed by measuring how quickly the server rejected passwords. The fix makes sshd run the full password hash even for nonexistent users and for accounts whose stored field is too short to hold a salt, such as *. To do that, sshd borrows a salt from another account on the system that has a real hash.
The Alpine advisory spells out the consequence. If at least one other local account has a $5$ (SHA-256) or $6$ (SHA-512) hash, sshd uses that salt. musl then runs its SHA-based crypt, which enforces the 256-byte cap.
Without PAM, sshd checks a password by hashing the attempt with crypt() and comparing the result to the stored field. For a locked * account and a long password, the sequence is:
*, which is too short to contain a salt.sshd borrows a $6$ (or $5$) salt from another account.crypt() fails and returns "*".sshd compares "*" (the error marker) with "*" (the stored lock marker).PAM-based configurations avoid this because, per sshhole.com, PAM never calls crypt() for a * account in the first place. The researcher also explained why nobody caught it: *"openssh tests on openbsd and glibc, musl doesn't test sshd, and most distros go through pam, which never even calls crypt for a ."
The CVE classifies the flaw as CWE-394: Unexpected Status Code or Return Value. That fits: sshd read an error value as if it were a real password hash.
The following simplified sketch shows the logic flaw. It is illustrative only, not verbatim OpenSSH source:
1/* Simplified illustration of pre-10.6p2 non-PAM password checking.2 * NOT verbatim OpenSSH code. */3char *pw_password = shadow_pw(pw); /* "*" for a locked / key-only account */45/* Stored field too short to hold a salt, so xcrypt() borrows one from6 * another account (e.g. "$6$...") as CVE-2016-6210 timing protection. */7char *encrypted = xcrypt(password, pw_password);89/* On musl, a password > 256 bytes makes SHA-crypt fail and10 * crypt() returns the literal string "*" instead of NULL. */1112return encrypted != NULL &&13 strcmp(encrypted, pw_password) == 0; /* "*" == "*" -> SUCCESS */The upstream commit (4353fdc43ef646c43a8dda7025cc1a898caf1482 in openssh-portable) adds a minimum length for stored password hashes, MIN_PASSWORD_HASH_LEN 4. sshd now requires strlen(pw_password) >= MIN_PASSWORD_HASH_LEN before it compares anything, so a one-character stored field like * can never authenticate. The commit touches auth-passwd.c, openbsd-compat/xcrypt.c and openbsd-compat/port-uw.c. It also fixes how strings returned by shadow_pw() are handled, so the caller now owns and frees them. The following sketch is illustrative:
1/* Illustrative sketch of the 10.6p2 workaround (see upstream commit2 * 4353fdc43ef646c43a8dda7025cc1a898caf1482 for the exact change). */3#define MIN_PASSWORD_HASH_LEN 445/* A stored "hash" shorter than this cannot be a real hash6 * ("*", "!", "x", etc.), so it must never match a crypt() result. */7if (strlen(pw_password) < MIN_PASSWORD_HASH_LEN)8 return 0; /* fail closed */910encrypted = xcrypt(password, pw_password);11return encrypted != NULL && strcmp(encrypted, pw_password) == 0;All of the following conditions must hold:
sshd linked against musl libc.--with-pam but runs with UsePAM no. On Alpine, that means the openssh-server package, or openssh-server-pam configured with UsePAM no.PasswordAuthentication enabled.*.$5$ or $6$ hash for sshd to borrow a salt from, per the Alpine advisory.PermitRootLogin yes. OpenSSH's default, prohibit-password, blocks root password logins.These preconditions explain the CVSS "Attack Complexity: High" rating. Once they are met, however, the attack itself is trivial. The Alpine advisory rates exploitability as easy, and sshhole.com describes the login bypass as low-complexity.
Combining the Alpine advisory, sshhole.com and the two public reproduction labs:
sshd that accepts password authentication and runs on a musl-based system without PAM, such as a default Alpine host or an Alpine tiny-cloud image.*. Candidates include the tiny-cloud default alpine user, root if PermitRootLogin yes, key-only users, or musl-nscd users.$5$ or $6$ hash.crypt() fails on the oversized input and returns *.sshd compares * to *, gets a match, and opens a session as the target user.alpine user with passwordless doas (Alpine's lightweight sudo equivalent), the attacker now controls the whole machine.Two GitHub repositories provide disposable Docker environments for confirming the issue and testing defenses. Run them only against systems you own.
From leonjza/sshhole-lab, the published commands are:
1# Start the vulnerable lab container (Alpine + vulnerable OpenSSH)2docker run --rm -it -p 2222:22 ghcr.io/leonjza/sshhole-lab:latest34# In another terminal, connect forcing password authentication5ssh -o StrictHostKeyChecking=no \6 -o PreferredAuthentications=password \7 -o PubkeyAuthentication=no \8 -p 2222 alpine@localhost910# When prompted, enter any password longer than 256 characters - twice.The predkambrij/sshhole-repro project is a self-contained Docker PoC. According to its analysis, it:
* in /etc/shadow;SSH_ASKPASS (with SSH_ASKPASS_REQUIRE=force, which requires an OpenSSH client 8.4 or newer) to feed two password attempts in one connection: a first attempt that primes the cached fake salt, then 300 A characters.It includes both non-root and root variants. The root variant also sets PermitRootLogin yes. The client options it relies on look like this:
1# Client-side options used by the repro to force two password attempts2# in a single connection (lab use only)3ssh -o PubkeyAuthentication=no \4 -o PreferredAuthentications=password \5 -o NumberOfPasswordPrompts=2 \6 -p <lab-port> <locked-user>@<lab-host>78# The second password supplied is an overlong string, e.g.:9python3 -c 'print("A" * 300)'Google's open-source goonami-scanner gained detection through pull request #277. That PR maps its opensshauthbypass detector to CVE-2026-123158.
Upstream OpenSSH
Vendor | Product | Affected Versions | Fixed Versions |
|---|---|---|---|
OpenBSD | OpenSSH (Portable) | All versions before 10.6p2, on Linux with musl libc and no PAM (or | 10.6p2 (commit |
Alpine Linux openssh-server packages (from the Alpine advisory)
Alpine Branch | Affected | Fixed Package | Included In |
|---|---|---|---|
edge | < 10.6_p2-r0 | 10.6_p2-r0 | n/a |
3.24 | < 10.3_p1-r2 | 10.3_p1-r2 | Alpine 3.24.4 |
3.23 | < 10.2_p1-r1 | 10.2_p1-r1 | Alpine 3.23.8 |
3.22 | < 10.0_p1-r11 | 10.0_p1-r11 | Alpine 3.22.8 |
3.21 | < 9.9_p2-r1 | 9.9_p2-r1 | Alpine 3.21.10 |
3.20 | < 9.7_p1-r6 | 9.7_p1-r6 | n/a |
3.19 | < 9.6_p1-r3 | 9.6_p1-r3 | n/a |
3.18 | < 9.3_p2-r4 | 9.3_p2-r4 | n/a |
3.17 | < 9.1_p1-r7 | 9.1_p1-r7 | n/a |
3.16 | < 9.0_p1-r6 | 9.0_p1-r6 | n/a |
Other Alpine components
Vendor | Product | Affected | Fix / Mitigation |
|---|---|---|---|
Alpine Linux |
| Same branch ranges as above when configured with | Same fixed versions; switching to |
Alpine Linux | tiny-cloud images | 3.18.11 through 3.24.3 | Updated images and patched packages (Alpine did not list exact fixed image versions) |
The leonjza/sshhole-lab analysis independently confirmed that Alpine Linux 3.24.1's package 10.3_p1-r1 is vulnerable.
Not affected, per OpenSSH and Damien Miller:
The CNA record's defaultStatus is "unaffected."
A scanner warning. The CPE in the CVE record, cpe:2.3:a:openbsd:openssh:*:*:*:*:*:*:*:* with versionEndExcluding 10.6p2, cannot express the musl and no-PAM conditions. Expect version-based scanners to flag nearly every OpenSSH install older than 10.6p2, including glibc servers that are not vulnerable. Confirm the libc and PAM configuration before you treat a finding as real.
The affected population is narrower than "all of OpenSSH," but it matters. Alpine is one of the most widely used base images in containers, a favorite for minimal cloud VMs, and common on appliances and edge devices. The intelligence gaps flagged across sources include whether other musl-based distributions ship OpenSSH without PAM in a vulnerable way. If you run any musl-based system, check it yourself rather than assuming Alpine is the only case.
As of publication, none of the sources report confirmed in-the-wild exploitation, and no threat actor or campaign has been tied to this bug. CISA's SSVC rating is poc, not active.
That status may not last. The usual gap between disclosure and weaponization did not exist here. The vulnerability, a detailed root-cause write-up, two turnkey Docker labs and a scanner module all became public on the same day. The "exploit" is an SSH client and a long string, so anyone who can type can use it. CISA rates it non-automatable because of the per-host preconditions, not because the attack is hard.
The most dangerous scenarios in the reference material:
alpine user has a * password field and passwordless doas. If first-boot or user-data provisioning creates any other user with a $6$ password hash, the alpine user becomes remotely accessible with any 257-to-1024-byte password, and passwordless doas makes that instant root. Alpine recommends hardening provisioning so it does not create a single user with a $6$ hash.* since 2022. Any host that combines a vulnerable sshd, PermitRootLogin yes, password authentication, and at least one other user with a real password hash is open to unauthenticated remote root.* precisely because they are meant to be key-only, so the accounts people consider most locked down are the exposed ones.Once inside, an attacker can do whatever the compromised account can do. The impact assessments list data theft, malware and persistence, new user accounts or authorized_keys entries, lateral movement, theft of cloud metadata and user-data secrets, container-escape attempts from privileged workloads, and service disruption.
The logs won't flag it. A successful bypass appears in the logs as an ordinary Accepted password for <user> from <ip> line. The Damien Miller analysis notes that password length is not logged, so defenders have to spot these events by context, such as a password login to an account that has no password.
On each Linux host running sshd, check four things.
1# 1. Is sshd linked against musl?2ldd "$(command -v sshd)" 2>/dev/null | grep -i musl34# 2. What is the effective config? (password auth, PAM, root login)5sshd -T 2>/dev/null | grep -Ei '^(passwordauthentication|usepam|permitrootlogin) '67# 3. Which accounts are locked with a bare '*'? (potential targets)8awk -F: '$2 == "*" {print $1}' /etc/shadow910# 4. Which accounts have SHA-256/SHA-512 hashes? (potential salt donors)11awk -F: '$2 ~ /^[$][56][$]/ {print $1}' /etc/shadowA host with musl, passwordauthentication yes, usepam no (or no PAM at all), at least one * account, and at least one $5$ or $6$ account is in the danger zone. To scan at scale, use the goonami-scanner opensshauthbypass detector (PR #277).
openssh package for your branch, using the table above:1apk update2apk upgrade openssh-server openssh-server-pam openssh34# CRITICAL: upgrading the package does NOT replace the running daemon5rc-service sshd restartThe Alpine advisory stresses that upgrading the package does not replace the running sshd process. Until you restart the daemon, the vulnerable code keeps running. For containers and cloud images, rebuild on patched base images and redeploy rather than patching running instances in place. Tiny-cloud users should move to updated images.
OpenSSH's own advisory recommends, as an emergency step, setting PasswordAuthentication=no immediately or disabling SSH entirely on affected systems until they are patched. In order of preference:
1printf 'PasswordAuthentication no\n' > /etc/ssh/sshd_config.d/00-cve-2026-123158.conf2sshd -t && rc-service sshd restart3sshd -T | grep -i passwordauthentication # should print: passwordauthentication noopenssh-server-pam and set UsePAM yes. PAM never calls crypt() for * accounts. Test this first, because it changes your authentication stack.! instead of * (full mitigation for those accounts, per Alpine). The Alpine advisory notes that user:!: accounts cannot log in with a password or a key. Don't apply this to accounts that need SSH keys, which is the exact reason Alpine moved root to * in 3.16. sshhole.com also suggests other lock markers such as !*, but only after you validate the behavior yourself.$5$ and $6$ hash from local accounts (full mitigation, per Alpine). Without a salt donor, the bypass fails. This is rarely practical, because it eliminates local password logins entirely.PermitRootLogin at prohibit-password (the default) or no. This protects root only; other * accounts stay exposed.sshd with firewalls, allowlists, VPN or bastion hosts. This reduces exposure but does not fix the bug.Treat any host that ran a vulnerable configuration while reachable from untrusted networks as potentially compromised. Alpine logs to /var/log/messages by default; other systems may use /var/log/auth.log or journalctl.
1# Look for password logins at all, then flag ones for '*' / key-only accounts2grep 'Accepted password for' /var/log/messages /var/log/auth.log 2>/dev/null34# Cross-reference against accounts that have no real password5for u in $(awk -F: '$2 == "*" {print $1}' /etc/shadow); do6 grep -h "Accepted password for $u " /var/log/messages /var/log/auth.log 2>/dev/null7doneRed flags to look for:
Accepted password for entries for root, alpine, key-only users, or any account whose shadow field is *. On an unaffected system, these accounts should never authenticate by password.doas or sudo activity from accounts that normally never log in interactively.~/.ssh/authorized_keys files, and unfamiliar files or cron jobs.No official Sigma, YARA or IDS signatures were published with the disclosure. A simple SIEM rule (alert on any successful SSH password authentication for a user whose shadow field is *, especially root, on a musl host) covers the core signal.
If you find signs of compromise:
sshd is the one actually running.Date | Event |
|---|---|
1980 | Bill Joy introduces |
Aug 2012 | musl commit |
Aug 2012 (three weeks later) | musl commit |
2013 | Debian bug #714219 weighs copying musl's |
2016 | OpenSSH fixes CVE-2016-6210 by hashing invalid and |
2022 | Alpine 3.16 switches root's shadow field from |
Dec 2024 | musl list thread notes BusyBox |
Nov 2026 | Özgün Kültekin (Wakefield) finds the bug through ~17,600-login differential testing |
Nov 4, 2026 | OpenSSH developers learn of the issue |
Nov 5, 2026 | OpenSSH 10.6p2 released; Alpine ships fixed packages; CVE-2026-123158 published (15:46 UTC); CISA SSVC added (17:12 UTC); PoCs, labs and scanner published |
The flaw comes from four safety decisions, each sensible on its own:
Together they formed a remote root hole that sat in plain sight for years, and one dropped mailing-list thread in 2024 nearly exposed it. Nobody wrote an obviously wrong line of code. The failure came from projects making different assumptions about what a function's return value means. That is CWE-394 at its purest, and it is a reminder that code returning a sentinel value instead of a clear error can end up matching real data.
A few lessons stand out for defenders:
*-account combination is. Asset inventories that record only package versions will get this one wrong in both directions.sshd. Every "we don't need that" decision creates a less-traveled code path.Watch for:
poc to active;In the meantime, the practical rule for Alpine and other musl-based hosts is simple: an account locked with * should never log in with a password, so investigate any log entry showing that it did.