SSHole: The OpenSSH Bug That Turns "No Password" Into Root

A long enough password unlocks accounts that were never meant to take one. Here is who is exposed and how to close it today.

Featured image

We assume an account with no password is a safe account. On Alpine Linux, root ships with a * in its password field precisely so that nobody can log in with a password. Key-only deploy users are created the same way. That assumption broke on 5 November 2026.

A flaw now tracked as CVE-2026-123158 and nicknamed “SSHole” lets an unauthenticated attacker log in over SSH to exactly those accounts, simply by sending a password that is between 257 and 1,024 bytes long. OpenSSH shipped an emergency release, 10.6p2, the same day. Within hours, public proof-of-concept code appeared, and by the next morning researchers at Previdian said they had watched an attacker get root on an Alpine honeypot using it.

The good news: the bug only bites a specific combination of settings. The bad news: that combination is the default on Alpine, which sits underneath a huge share of the Docker images, appliances and embedded devices in use today. This post explains who is affected, how to check in minutes, and what to fix first.

TL;DR / Key takeaways

  • What: CVE-2026-123158 (“SSHole”, CVSS 8.1) is an authentication bypass in OpenSSH on Linux systems built with the musl C library and without PAM. An oversized password (257–1,024 bytes) is accepted for accounts whose password hash is *.
  • Who: Alpine Linux (every release checked, 3.16 to edge), plus other musl-based builds such as some Buildroot and Yocto images, and any Docker container that runs sshd on Alpine. glibc distributions (Debian, Ubuntu, RHEL, Fedora), PAM-enabled setups, OpenBSD and Dropbear are not affected.
  • Exploitation: Public PoCs appeared within hours. Previdian reported root access on an Alpine honeypot less than nine hours after the CVE was published, and has since logged 19 attempts from 12 IPs.
  • Fix: Upgrade to OpenSSH 10.6p2 or your distro’s patched package (Alpine: apk upgrade -U -a), then restart sshd. Quick workaround: PasswordAuthentication no.
  • Then check: Search logs for Accepted password for on accounts that should never accept passwords. If you cannot rule out a login, treat the host as compromised.

Timeline

How SSHole works (conceptually)

To understand the bug you need three small facts.

1. * means “no password”. In /etc/shadow, a password field of * means the account cannot log in with a password at all. It is how Alpine marks root, how adduser marks a user created without a password, and how many key-only service accounts are set up.

2. sshd still hashes your password for those accounts. Since the 2016 enumeration fix, when PAM is not in use, sshd takes the salt from another account on the system that has a real hash (a $5$ or $6$ entry) and runs your password through crypt() anyway. The result is then compared with what is stored in /etc/shadow.

3. musl’s crypt() returns * when it fails. musl rejects passwords longer than 256 bytes for these hash types. When it does, it does not return an empty string or crash. It returns the string *.

Put those together: the attacker sends a 257-byte (or longer, up to 1,024) password for an account stored as *. musl fails, returns *, and sshd compares * with *. They match, and the login is accepted. The OpenSSH team noted that they consider “the root cause of this bug to be outside sshd, but it is exploitable via sshd and can be avoided there.” Their fix makes sshd refuse a match when the stored hash field is shorter than four characters.

Important limits, which come straight from the advisories:

As Safe Security summarised it: “A successful SSHole attack grants access with the compromised account’s existing permissions.” On Alpine, that is very often root.

Why cloud images are a special case

Alpine’s advisory flags its official “tiny-cloud” images for AWS and Azure. Images from 3.18.11 through 3.24.3 ship an alpine user with a * password and passwordless doas. They are safe on first boot because no $6$ hash exists yet. The moment an admin sets a console password for any account, the box becomes exploitable, and the alpine user can become root. Images built from 3.21.10, 3.22.8, 3.23.8, 3.24.4 and later include the fix.

Real-world exploitation

The disclosure-to-attack window here was measured in hours, not weeks.

No named threat actor has been linked to the activity so far, and details are still emerging. The pattern is familiar: mass scanning of port 22 with a cheap one-shot payload.

Am I affected?

You are potentially vulnerable only if all four of these are true on a host (per the Alpine advisory):

  1. sshd is OpenSSH built without PAM, or the PAM build with UsePAM no, on a musl system.
  2. PasswordAuthentication yes (the default).
  3. At least one account has * as its password hash.
  4. At least one other account has a $5$ or $6$ hash.

You are not affected if you run glibc distributions such as Debian, Ubuntu, RHEL or Fedora; use PAM (UsePAM yes); use Dropbear (the OpenWrt default) instead of OpenSSH; run containers that do not run sshd; or already have PasswordAuthentication no. Alpine notes that cloud-init images write PasswordAuthentication no and are therefore not exposed.

Quick checks you can run on a suspect host (read-only, as root):

~|#| 1. Is this a musl system, and which OpenSSH is installed?
ldd ~|-|~|-|version 2>&1 | head ~|-|1          ~|#| mentions "musl" on Alpine
apk info ~|-|v openssh~|-|server 2>/dev/null
ssh ~|-|V

~|#| 2. What is the effective password~|-|auth setting?
sshd ~|-|T | grep ~|-|Ei '^(passwordauthentication|usepam|permitrootlogin)'

~|#| 3. Which accounts use "*" and which have real $5$/$6$ hashes?
awk ~|-|F: '$2~|=|~|=|"*"{print "star:", $1}' /etc/shadow
awk ~|-|F: '$2 ~ /^\$[56]\$/{print "hash:", $1}' /etc/shadow

If step 2 shows passwordauthentication yes and step 3 lists both star: and hash: entries, patch now.

For containers, the question is simply whether the image starts sshd. Check your Dockerfiles and compose files for openssh-server and an sshd entrypoint, or run pidof sshd inside running containers.

Alpine’s fixed openssh packages, per branch: 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. Point releases 3.21.10, 3.22.8, 3.23.8 and 3.24.4 include the fix. Note that only edge moves to 10.6p2; the stable branches received a backport, so do not judge exposure by the upstream version string alone.

Detection: did someone already get in?

Normal SSH logs do not record password length, so you cannot see the oversized password itself. What you can see is the result: a password login on an account that should never accept one.

~|#| Alpine default syslog location
grep 'Accepted password for' /var/log/messages

~|#| Narrow to accounts that should be key~|-|only or passwordless
grep ~|-|E 'Accepted password for (root|alpine|deploy|git)\b' /var/log/messages

Adjust the user list to match your key-only accounts. In a SIEM, the same idea looks like this:

~|#| Splunk
index~|=|linux sourcetype~|=|syslog "Accepted password for"
| rex "Accepted password for (?~|<|user>\S+) from (?~|<|src_ip>\S+)"
| search user IN ("root","alpine","deploy","git")
| stats count min(_time) as first max(_time) as last by host user src_ip

~|#| Microsoft Sentinel (Syslog table)
Syslog
| where ProcessName ~|=|~|=| "sshd" and SyslogMessage has "Accepted password for"
| extend user ~|=| extract(@"Accepted password for (\S+)", 1, SyslogMessage),
         src_ip ~|=| extract(@"from (\S+)", 1, SyslogMessage)
| where user in ("root","alpine","deploy","git")
| summarize count(), min(TimeGenerated), max(TimeGenerated) by Computer, user, src_ip

Also review, as Alpine recommends:

Alpine’s guidance is unambiguous: “If you cannot rule out a login, treat the machine as compromised.” For a container, that means rebuild from a clean, patched image and rotate any secrets it held. For a VM or appliance, it means incident response, not just a patch.

What to do: a prioritised checklist

Immediately (today)

  1. Patch and restart. On Alpine: apk upgrade -U -a, then rc-service sshd restart. The upgrade does not restart the running daemon, so an unrestarted host stays vulnerable. Elsewhere, install OpenSSH 10.6p2 or your vendor’s fixed build.
  2. If you cannot patch right now, turn off password auth. OpenSSH’s Damien Miller put it plainly: “set PasswordAuthentication=no or disable SSH for these systems IMMEDIATELY.” Alpine suggests a drop-in file (read by default from 3.19):

    echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00~|-|no~|-|passwords.conf
    sshd ~|-|t && rc~|-|service sshd restart
    sshd ~|-|T | grep ~|-|i passwordauthentication   ~|#| expect: passwordauthentication no
    

    Check Match blocks in sshd_config too: a Match section can switch password auth back on for some users.

  3. Run the log check above on every affected host and escalate any unexpected Accepted password line.

This week

  1. Rebuild container images. Any Alpine-based image that runs sshd must be rebuilt on a fixed base and redeployed. Patching a running container does not fix the image the next one starts from.
  2. Inventory musl systems beyond Alpine: Buildroot and Yocto-based devices, network and storage appliances, CI runners and “jump box” containers. Ask embedded vendors whether they are affected.
  3. Rebuild or re-image Alpine cloud instances created from tiny-cloud images 3.18.11 to 3.24.3, or at least patch them and confirm no console password has been set on a * account with passwordless doas.
  4. Take SSH off the internet where it does not need to be there. Put it behind a VPN, bastion or identity-aware proxy, and restrict source IPs at the firewall or security group.

Longer term

  1. Make key-only the norm. Set PasswordAuthentication no and KbdInteractiveAuthentication no in your baseline images unless you have a specific reason not to, and test it in CI with sshd -T.
  2. Stop shipping sshd in application containers. Use docker exec, kubectl exec or your platform’s session tooling instead. A container that does not run sshd is not affected by this bug or the next one.
  3. Prefer ! for locked accounts and review how your build tooling creates service users.

What this means for Pakistan and South Asia

Alpine is the default base for a large share of the Docker images that startups, fintechs and e-commerce teams in Pakistan deploy on local VPS providers and in AWS or Azure regions nearby. Debug containers running sshd and VPS images with password login enabled are easy to forget about. The same goes for network gear, NVRs and IoT boxes built on Buildroot or Yocto that are rarely patched once installed. If your team builds or sells such devices, check your firmware now; your customers will not know to.

OffensioX perspective

SSHole is a textbook example of how security bugs live in the seams between components. musl’s behaviour was documented a decade ago. OpenSSH’s anti-enumeration fix was a sensible security improvement. Alpine’s choice of * for root was reasonable. Each decision was defensible on its own; combined, they produced a remote root login that no one noticed for years. It also shows how much the window has shrunk: public PoC within hours, honeypot compromise the next morning. The lessons we would underline are hygiene ones that would have prevented exposure regardless of this specific bug: no password authentication on internet-facing SSH, no sshd inside application containers, and an asset inventory good enough to answer “where do we run Alpine with SSH?” in minutes rather than days.

If you are not sure what your SSH exposure looks like across servers, containers and appliances, OffensioX’s penetration testing and security assessments can map it and verify your fixes hold.

References