Security

OpenSSH’s ‘sshole’ Is a Decade-Old Three-Way Collision—Alpine Containers Make It Urgent

Updated

OpenSSH’s ‘sshole’ Is a Decade-Old Three-Way Collision—Alpine Containers Make It Urgent
Uploaded by the desk

CVE-2026-123158—“sshole”—is not a clever new crypto break. It is a decade-long collision among OpenSSH’s 2016 anti-enumeration salt borrowing, musl’s crypt() failure string, and Alpine’s default non-PAM openssh-server. Fixed in OpenSSH 10.6p2 on November 5, 2026, it became urgent because Alpine dominates containers and cloud tiny images where a passwordless doas path can turn a locked-looking account into root.

On November 5, 2026, Portable OpenSSH shipped 10.6p2 with what the project called a critical security workaround for Linux systems that use musl libc with PAM disabled. The same day, Alpine Linux published a security advisory and patched packages across supported and unsupported branches. The issue is tracked as CVE-2026-123158 / GHSA-4fcc-vm97-gx3x (CVSS v3.1 8.1 High in the GitHub Advisory Database) and nicknamed “sshole” by discoverer Özgün Kültekin of Wakefield, who published the mechanism at sshhole.com.

Alpine’s own advisory puts the operational stakes bluntly: an attacker who can reach sshd can authenticate to eligible passwordless accounts without knowing any password. That claim is scoped, not universal. The bypass hits a specific configuration stack—and that stack is common precisely where Alpine is popular.

Three decisions, one strcmp

No single component invented a new authentication protocol. Three older choices lined up.

First, Unix still marks many “never password-login” accounts with a single asterisk (*) in the shadow password field. That string is not a usable hash; under normal conditions, no submitted password should match it. Alpine’s root has shipped that way since about 3.16, and key-only users often look the same.

Second, musl’s crypt()—the C library routine that turns a password plus salt into a hash—returns the string * when hashing fails. Since 2012, musl has also refused SHA-crypt keys longer than 256 bytes as a defense against CPU exhaustion. Oversize input therefore fails, and failure returns *.

Third, OpenSSH’s 2016 response to username enumeration (CVE-2016-6210) changed the non-PAM password path. When an account has no usable stored hash, sshd still performs a hash using a salt borrowed from another local account so timing does not leak whether a username exists. That borrowed salt is typically a real $5$ or $6$ (SHA-256 / SHA-512 crypt) prefix from some other user who once ran passwd.

At a high level, the collision is mechanical: sshd borrows a real salt for a * account, musl’s crypt() fails on an overlong password and returns *, and a string compare treats that failure marker as a match against the stored *. Accounts that already hold a real password hash are not bypassed this way. The OpenSSH project’s release notes say the root cause sits outside sshd, but the bug is exploitable through sshd and avoidable there—hence the 10.6p2 workaround, which refuses to treat implausibly short stored fields as comparable hashes (upstream commit 4353fdc43).

Who is actually exposed

Exposure requires every condition below, not a vague “Alpine somewhere in the stack.”

  • Linux with musl libc (Alpine, and OpenSSH on musl-based OpenWrt / Buildroot / Yocto builds—not Dropbear, which is a different SSH server and is not this bug).
  • sshd built or run without PAM. Alpine’s default openssh-server package is in that category; openssh-server-pam with UsePAM yes is not affected.
  • PasswordAuthentication yes (OpenSSH’s default unless changed).
  • At least one account whose shadow field is exactly * and that SSH policy allows to authenticate.
  • At least one other local account with a real $5$ or $6$ hash so a salt exists to borrow.

glibc-based systems such as Debian, Ubuntu, and Fedora are outside this collision. OpenBSD’s OpenSSH testing surface and PAM-mediated checks also miss the musl failure-string path. CERT Santé’s November 6 alert and Alpine’s advisory agree on that boundary.

Root on Alpine often carries *, but OpenSSH’s default PermitRootLogin prohibit-password blocks direct root password login unless an admin has opened it. The larger practical risk Alpine and the researcher both emphasize is indirect privilege: another reachable * account that can escalate.

Why Alpine cloud and containers raise the stakes

Alpine’s dominance in container base images and lightweight cloud artifacts is why a libc-edge case became infrastructure news. The vulnerability is not “every Alpine container is owned.” Alpine’s advisory is explicit that Docker and other container users who do not run sshd inside the container are not affected by this sshd path. The risk concentrates where someone publishes or keeps OpenSSH password authentication on a musl host.

Official Alpine tiny-cloud images (qcow2, VHD, AMI) from 3.18.11 through 3.24.3 ship an alpine user with a * password field and passwordless doas. Alpine states those images are safe on first boot and become exploitable once any $6$ hash appears on the machine—for example after an admin sets a console password or user-data adds a hashed password. At that point, a successful login as alpine plus passwordless doas is effectively root. Images from 3.21.10, 3.22.8, 3.23.8, 3.24.4 and later include the fixed package; older running instances still need an upgrade or a password-auth shutdown.

That pattern—key-oriented cloud defaults plus a later convenience password hash—is how a “locked” field and a salt donor end up on the same box. It is also why CI images, debug sidecars, and mirrored APK caches pinned before November 5 matter more than a headline that every Alpine rootfs is doomed.

Exploitation within hours—attributed, not overclaimed

Help Net Security reported on November 6 that threat intelligence firm Previdian observed scanning and at least one successful root compromise on an Alpine honeypot less than nine hours after CVE publication. Previdian’s Ryan Dewhurst described oversized-password attempts from a single Japan-based source IP and reconnaissance over non-interactive SSH. That is Tier-2 industry telemetry: attribute it to Previdian via Help Net Security. As of those reports, CISA had not listed the CVE in its Known Exploited Vulnerabilities catalog. CERT Santé’s alert marked active exploitation as “No” while noting a public proof of concept—another reason to separate honeypot observations from a federal KEV listing.

Public PoCs exist; this article will not reproduce them. For defenders, the useful signal Alpine and SOC guidance highlight is simpler: Accepted password log lines for accounts that should never accept passwords (for example alpine, key-only deploy users, or root when password login should be impossible).

What to do now

Defensive steps only:

  • Upgrade OpenSSH to 10.6p2 or newer upstream, or install Alpine’s fixed package for your branch (edge 10.6_p2-r0; 3.24 10.3_p1-r2; 3.23 10.2_p1-r1; 3.22 10.0_p1-r11; and the backports Alpine published through 3.16). Then restart sshd—package upgrades do not replace the running daemon.
  • If you cannot patch immediately, set PasswordAuthentication no (including Match blocks), confirm key-based login works in a second session, run sshd -t, and restart sshd.
  • Prefer openssh-server-pam with UsePAM yes where that fit is operationally sound; lab write-ups such as StepSecurity’s November 6 analysis report that PAM path blocks the bypass even on a still-vulnerable binary.
  • Do not trust sshd -V alone on Alpine: backports keep the upstream version string while the package revision carries the fix. Ask apk info -v openssh-server (or the PAM package) and compare against Alpine’s fixed revisions.
  • Hunt auth logs for password acceptances on accounts whose shadow field is *; review doas history, new users, and authorized_keys on any host that may have been reachable.

The implication

sshole is a reminder that authentication safety is often an emergent property of libc, packaging defaults, and an old side-channel fix—not a single bad line written last week. OpenSSH 10.6p2 and Alpine’s same-day package sprint closed the strcmp trap. The remaining work is operational: find musl hosts that still run non-PAM sshd with password authentication on, restart patched daemons, and treat unexpected password logins to “passwordless” accounts as compromise until proven otherwise. In a world where Alpine underpins countless images, the decade-old collision mattered because the default stack made it reachable—not because every container suddenly grew a free root shell.