Security for startups

OpenSSH sshhole: What to Check on Alpine Linux

OpenSSH 10.6p2 shipped a workaround for sshhole on musl Linux with no PAM. Who is exposed, a five-minute Alpine check, the fix, and why there was no embargo.

TL;DR: The OpenSSH bug going around as “sshhole” needs a musl Linux host (Alpine is the common one), sshd without PAM, password logins on, an account whose shadow password field is exactly *, and a second account with a real password hash. Check with the five commands below. The fix is PasswordAuthentication no plus an upgrade to the patched openssh-server package. As of November 5, no CVE and no exploitation had been reported.

On November 5, 2026, the OpenSSH project released portable 10.6p2. The release notes call it “a critical security workaround that affects Linux systems that use the musl libc and PAM disabled.” A post on X boiled that down to: on Alpine, type a long enough password and you get root. That summary is wrong in a few places. The real exposure takes five conditions at once, and the default Alpine container config blocks the root case.

I’m a CISO and I do fractional security work for small AI startups. I ran the default Alpine setup in Docker on November 5 to see what it does. I did not run the exploit, and nothing below says it works or doesn’t on my lab box. Everything about the exploit mechanism comes from the reporter’s write-up.

Who is exposed?

Damien Miller’s announcement on Hachyderm says non-Linux systems, Linux with glibc, and Linux systems that use PAM for password authentication are not affected. That leaves Linux with musl libc and sshd built without PAM.

The reporter, Özgün Kültekin of Wakefield, published a write-up at sshhole.com. His account of the mechanism: musl’s crypt() returns * when it fails, instead of NULL. musl’s SHA-crypt refuses passwords longer than 256 bytes. So when a user logs in against an account whose shadow field is exactly *, a password of 257 to 1024 bytes gets treated as a match. The write-up says the * account “takes any password between 257 and 1024 characters.” It also needs another account on the box with a real password hash, so there is a salt to work with.

The conditions the write-up lists, all at once:

  • sshd running on musl (Alpine, OpenWrt, Buildroot and Yocto all fit)
  • no PAM, which is how Alpine’s openssh-server ships, or UsePAM no elsewhere
  • password logins turned on
  • an account whose shadow field is *: a key-only user, an LDAP user, or root on Alpine
  • some other account with a real password hash

The headlines got root wrong. The write-up says Alpine locked root with ! in 2019 after CVE-2019-5021, then changed root’s lock to * in Alpine 3.16 in 2022. So root is a * account on current Alpine. But the sshd_config man page says, for the prohibit-password setting of PermitRootLogin: “If this option is set to prohibit-password (or its deprecated alias, without-password), password and keyboard-interactive authentication are disabled for root.” That is the default, so root over SSH with a password needs someone to have set PermitRootLogin yes. Other * accounts are the ones to hunt for.

What I saw in a default Alpine container

I started alpine:latest in Docker on an arm64 Linux box, then ran apk add openssh-server.

  • /etc/alpine-release said 3.24.3.
  • Root’s shadow field was *. It was the only * account in the base image.
  • apk installed openssh-server 10.3_p1-r2, which is the fixed Alpine build for 3.24, even though the base image itself is 3.24.3.
  • sshd -T printed passwordauthentication yes and permitrootlogin prohibit-password, and no usepam line at all. Alpine builds sshd without PAM.
  • ldd /usr/sbin/sshd listed only musl (ld-musl) and no libpam.
  • adduser -D keyonly gave the new account ! in the shadow field, not *. So did passwd -l.

The two stock ways I tried of making a locked or key-only user both produced !. A * account mostly shows up when something else created it: an LDAP or sync tool, a hand-edited shadow file, a base image or appliance build. My reading is that default Alpine is a lot harder to hit than the posts suggest. Your hosts may not be default, so check them.

Is it being exploited?

As of November 5, The Hacker News reports no CVE assigned and no exploitation seen. Miller’s post still says to act fast. In his words, the advice is to “set PasswordAuthentication=no or disable SSH for these systems IMMEDIATELY.” That applies to systems that meet every condition above.

The five-minute check

Start with whether sshd runs at all. Most Alpine containers never start one, and a container built from the Alpine image with no sshd is not exposed through this bug.

pgrep sshd

If something comes back, run these on that host:

cat /etc/alpine-release
ldd /usr/sbin/sshd | grep -E 'musl|pam'
sshd -T | grep -Ei '^(usepam|passwordauth|permitroot)'
awk -F: '$2=="*"{print $1}' /etc/shadow
apk info -v | grep openssh-server

Read the results in order:

  1. ldd shows musl and no pam: sshd is the musl, no-PAM kind.
  2. sshd -T shows passwordauthentication yes and no usepam yes: password logins are on.
  3. The awk line prints account names whose shadow field is exactly *. Root will be on that list on Alpine. Anything else on it is a candidate. Root is only reachable if permitrootlogin shows yes.
  4. apk info -v shows which openssh-server build you have. On Alpine 3.24 you want 10.3_p1-r2 or later.

If the first three are all true, treat the host as exposed. It also needs a second account with a real password hash, which is easy to meet on any box where a human ever set a password.

The fix

OpenSSH’s change makes sshd refuse a stored hash shorter than 4 characters. The patch defines MIN_PASSWORD_HASH_LEN 4, and Miller’s upstream commit is dated November 5. Alpine picked it up for 3.24-stable, where the package went from pkgrel 1 to 2 and gained a pw.patch carrying that commit. Edge moved to 10.6_p2. The Hacker News lists fixed packages back to Alpine 3.16 and point releases 3.21.10, 3.22.8, 3.23.8 and 3.24.4. I only verified the 3.24 commit myself, so check the others against Alpine’s own package pages.

Turn off password logins first, since that closes the path without waiting on a package, then upgrade. Before you restart sshd, make sure you can still get in with a key.

echo 'PasswordAuthentication no' \
  > /etc/ssh/sshd_config.d/00-no-passwords.conf
rc-service sshd restart
apk update
apk upgrade openssh-server
rc-service sshd restart

On Alpine 3.24 the stock sshd_config has an Include /etc/ssh/sshd_config.d/*.conf line, and in my container sshd -T showed passwordauthentication no after adding that file. On an older or hand-built config, run sshd -T | grep passwordauth to confirm. If it still says yes, the file isn’t being read, and the setting goes in the main sshd_config.

Search your auth logs for Accepted password for. No password should ever work on an account whose shadow field is *, so any hit for root or a key-only user needs a closer look. No hits doesn’t clear the host, since logs get rotated or wiped.

Why a one-day fix matters

The release notes put the root cause outside sshd, and Miller says so too: “We consider the root cause of this bug to be outside OpenSSH itself”. So the project shipped a workaround, not a fix to the thing that returns *. They shipped it fast. His post says, “We are making this release as fast as possible (we learned of the problem yesterday)”.

He also explains why there was no long private window: “we have recently observed multiple cases of rapid bug rediscovery by AI agents, sometimes within hours”, and the project will “now consider long embargo periods to be a dangerous practice that primarily benefits attackers.” The write-up says its author used swarms of AI agents for build and login testing.

I covered the finding side of this in the decade-old bug that took a week to find, and the fixing side in Detection Got Cheap. Remediation Didn’t.. I won’t repeat either. This case adds a number from the maintainer: one day from report to release, and no more long embargoes.

For a small team that means the patch window is hours. The part you can prepare is knowing within minutes which hosts run sshd, and which of those are musl without PAM. The five commands above are a first pass at that list. I help small teams build that host list as part of my consulting work.

What I checked on my own stack

This site runs Elixir and Phoenix on Fly.io from a Debian-based image, which uses glibc, so it is not affected. If your Alpine hosts are all containers with no sshd, the answer for you may be the same. Confirm it with pgrep, then go look at routers and embedded builds, where musl and no PAM are more likely.

For another triage post in the same format, see php2shell: What to Do if Your Servers Run PHP.