On 5 November 2026 Alpine Linux published an advisory and OpenSSH shipped 10.6p2 the same day. On a musl host running sshd without PAM, an account whose shadow field is will accept a password it never had. musl's crypt() returns the string to signal an over-long key, and is also exactly what the shadow field holds for an account with no password, so the comparison succeeds. root on Alpine ships as root:. So do key-only users, LDAP/SSSD users with no shadow entry, and the alpine user in tiny-cloud images.
These checks are for whoever has to produce a defensible statement about Alpine in container images, bastions, CI runners and musl-based appliance firmware, and about whether anyone used the hole first. Three artifacts a review normally accepts fail on this bug: the OpenSSH version banner, the running daemon after apk upgrade, and the dependency scanner.
Short version: exposure needs four facts to hold at once (musl, no PAM, password auth on, a * account sitting next to a real $5$ or $6$ hash), the fix is the Alpine -rN package plus an sshd restart, and your SCA tool will stay silent because the advisory carries no package and no version range.
The checks that survive a review
- Decide exposure from four runtime facts, not from a CVE feed. All four must hold, per Alpine's advisory: the daemon links musl, PAM is out of the auth path, password authentication is on, and the host has a
*account plus at least one account with a real$5$or$6$hash, whose salt the attempt borrows. Run all three commands and record all three answers.
ldd /usr/sbin/sshd | grep -i musl
sshd -T | grep -iE '^(usepam|passwordauthentication|permitrootlogin)'
awk -F: '{print $1, substr($2,1,3)}' /etc/shadow
- Read the effective config with
sshd -T, never the file.PasswordAuthentication nositting in/etc/ssh/sshd_configproves nothing when anIncludedirectory, a cloud-init drop-in or a config-management snippet re-enables it further down. On my own server a cloud-init drop-in silently overrode that exact keyword for weeks while the main config file read correctly, and the dump was the only place it showed. Alpine's workaround tells you to verify against the dump too.
sshd -T | grep -i passwordauthentication # must print: passwordauthentication no
- Key your inventory on the
-rN, because the OpenSSH version number sorts it wrong. Alpine backported the fix across ten branches, so the patched builds are9.0_p1-r6on 3.16,9.9_p2-r1on 3.21,10.3_p1-r2on 3.24 and10.6_p2-r0on edge. A host whose banner readsOpenSSH_9.9can be either state, which is the same trap as php2shell and Node Brotli RCE: the version string lies, and the per-branch divergence matches core.fsmonitor RCE: 4 Agent Patches, 4 Different Key Lists.
apk policy openssh | head -20
apk info -v openssh openssh-server
- Check
/procbefore you call the host patched, then restart the daemon. Alpine leads its advisory with this:apk upgradereplaces the binary on disk and leaves the oldsshdresident in memory. The cheap fleet-wide proof is the exec link of the live process, which gains a(deleted)suffix once the file under it has been swapped.
ls -l /proc/$(pgrep -o -x sshd)/exe # "(deleted)" means patched on disk, vulnerable in RAM
rc-service sshd restart
- Expect your scanner to stay silent, and write that down before an auditor asks. GHSA-4fcc-vm97-gx3x carries a CVSS of 8.1 and, as printed today, "Affected versions: Unknown", "Patched versions: Unknown" and no package from a supported ecosystem, so GitHub states on the page that Dependabot alerts are unsupported for it. A version-range matcher has nothing to match, the same shape as the identifier problem in Fix Starlette CVE-2026-48710: 1.0.1 Misses the Sibling. What I could not confirm is whether Alpine's secdb feed has landed a fixed-version entry for every branch yet, and this query settles it per branch:
curl -s https://secdb.alpinelinux.org/v3.22/main.json | jq '.packages[] | select(.pkg.name=="openssh")'
- Hunt for use before you declare it closed. Alpine names the signal:
sshdlines in/var/log/messagesreadingAccepted password foran account that has no password. The advisory's instruction when that search is inconclusive is to treat the machine as compromised, so pair the grep with a review ofdoasinvocations, newly created users and recently touchedauthorized_keysfiles.
grep -E 'sshd.*Accepted password for (root|alpine)' /var/log/messages
find /home /root -name authorized_keys -newermt '2026-10-20' -ls
- Do not "lock" the key-only accounts to fix it. Swapping
*for!reads as the tidy hardening move and it closes the wrong door: Alpine's advisory notes that an account with!cannot authenticate by password or by key, so the edit also breaks the automation that used that account's key. Turn password authentication off and leave the shadow field alone. - Price the break-glass you are about to remove.
PasswordAuthentication nois the durable control, and on a console-less VM or an appliance with no serial path it also deletes your last recovery route, which is how a key change turns into a dead host (see x509: no valid chains built: Go 1.27.3 blocks 8 keys). Name the replacement before the change window: a provider console, an out-of-band path, or a second key held by a separate custodian. One row I have not verified is whetherKbdInteractiveAuthentication yesoffers a second route into the same comparison on a PAM-less build, and the check is to read the device table inauth2-chall.cfor the build you ship, since without PAM, bsdauth or skey it should register no devices at all. - Inventory the
sshdinstances you forgot you owned. The disclosure page lists musl-based projects beyond Alpine, including OpenWrt, Buildroot and Yocto, which puts this bug in appliance and device firmware you cannotapk upgrade. Tiny-cloud images from 3.18.11 to 3.24.3 ship analpineuser with a*password and passwordlessdoas, so enumerate from the registry and the cluster rather than from a spreadsheet.
kubectl get pods -A -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | sort -u
docker run --rm --entrypoint sh IMAGE -c 'apk info -v openssh-server 2>/dev/null; ldd /usr/sbin/sshd 2>/dev/null'
- For a vendor appliance, get three answers in writing and a network control today. Ask whether the firmware links musl, whether
sshdruns with PAM, and whether password authentication is enabled. Those three determine the verdict and no vendor can reasonably decline to answer them. Until a firmware build arrives, restrict port 22 to a bastion CIDR or a management VLAN and log the restriction with a date, so the compensating control is an artifact rather than a memory. - Treat the class, not the instance. GitHub files this as CWE-394, "Unexpected Status Code or Return Value": musl's crypt_sha512.c defines
KEY_MAX 256, rejects a longer key, and the__crypt_sha512wrapper hands back the string"*", a sentinel the caller then compared as data. Upstream's commit 4353fdc is four lines of defensive minimum,MIN_PASSWORD_HASH_LEN 4applied insys_auth_passwd()ahead of thestrcmp. The habit that catches this family is probing behaviour instead of grepping versions: on the October Brotli patch day I ran the decoder probe against my own box before recommending it to anyone, and the release number said patched while the error code (-9) said otherwise on an end-of-life Node 20 host.
Wrap-up
Separate patched on disk from patched in the process, and make the second one the artifact you record. The gap between those two states here is an apk upgrade with no restart, which is the most common way a host shows green in a report and still accepts the login. Build the per-host row now: musl yes/no, PAM yes/no, passwordauthentication as printed by sshd -T, package -rN, daemon restart time, log review timestamp. A version scan cannot produce that row, and it is what an auditor accepts.
If the honest answer is that you cannot enumerate every musl sshd in your estate this week, that gap is scoped work: the inventory, the proof row for each host, and the compensating controls for the appliances nobody can patch. The services page lists the packages, and the scoping form there is the fastest way to get a fixed quote.
What I read while writing this: Alpine's advisory, OpenSSH 10.6p2 release notes, and the musl crypt_sha512.c source linked above.
Comments
Be the first to comment.