sshhole (CVE-2026-123158): update OpenSSH on Alpine and other musl Linux now
· Witen Labs · Security advisory
CVE-2026-123158, nicknamed sshhole: OpenSSH on Alpine and other Linux systems built on the musl C library has let accounts that should never take a password log in with any password longer than 256 characters. It has been possible since 2016. OpenSSH 10.6p2 and Alpine's patched packages, both released November 5, fix it.
What to do: Update OpenSSH today on every Alpine, OpenWrt, Buildroot, Yocto, or musl-based container that runs sshd, and restart sshd. Alpine’s fixed packages are out, with details in Alpine’s advisory. Can't update yet? Turn password logins off. Then check your logs for a login that should be impossible, and upgrade to Warden 0.1.46, which flags exposed servers and alerts on exactly that login.
Blocking tools can’t stop this one
Warden can’t block this attack, and neither can fail2ban, DenyHosts, or a network firewall that lets the world reach port 22. Only fixing sshd closes it.
- The password travels inside the encrypted SSH session, so nothing on the network can see that it is 300 characters long.
- The attack takes one connection. Alpine’s advisory says it works every time and needs no retries, so there are zero failed attempts to count. fail2ban bans an address after 5 failures by default, and rate limits count failures too; neither ever sees one.
- One successful login is the whole attack. There is no flood of failures to notice first.
The one network control that helps is not letting the internet reach SSH at all: a firewall that only allows your own addresses to port 22, or SSH reachable only through a VPN. Do that alongside updating OpenSSH and turning off password logins, not instead of them.
What Warden does is the other half of the job: find vulnerable servers before an attacker does, and raise the alarm if someone already got in.
What happened
Özgün Kültekin at Wakefield found the bug and explains its history in detail at sshhole.com. In short, three reasonable decisions add up to a hole:
- A
*means "no password, ever." Since early Unix, a*in an account’s password field means no password can match, so the account only logs in another way, such as an SSH key. Key-only deploy users, LDAP users, and root on Alpine look like this. - musl’s password hashing fails with a
*. When musl’scrypt()can’t hash a password, it returns*instead of an error, and it refuses passwords longer than 256 bytes. - sshd hashes even when there is nothing to check. To stop attackers from timing logins to learn which usernames exist, sshd hashes the password for accounts without a usable hash anyway, using another account’s salt.
Put together: sshd hashes a long password for a * account, musl fails and hands back *, and * matches the stored *. sshd accepts the login. Any password between 257 and 1,024 characters works.
It needs all of these at once:
- sshd runs on musl (Alpine, OpenWrt, Buildroot, Yocto, and many small Docker images);
- sshd was built without PAM, which is Alpine’s default
openssh-serverpackage, or uses Alpine’sopenssh-server-pamwithUsePAM no; - password logins are on, which is OpenSSH’s default;
- an account has
*as its password; - another account has a real password hashed with SHA-256 or SHA-512 (a
$5$or$6$hash, the usual kind), which supplies the salt.
Root is safe on a default setup, because OpenSSH refuses password logins for root unless PermitRootLogin yes is set. The accounts at risk are the other * accounts. On Alpine’s official AWS and Azure images, the researcher reports, that is the default alpine user, which can run commands as root without a password through doas.
Debian, Ubuntu, RHEL, Fedora, and other glibc systems are not affected, and neither is any sshd that checks passwords through PAM (including Alpine’s openssh-server-pam with UsePAM yes), Dropbear, or OpenBSD.
Why it matters: SSH never stops getting guessed
Every server with SSH open to the internet is guessed at all day. On our customers’ servers that run Warden, over the 30 days before the disclosure:
- About 1,000 failed password guesses a day, roughly 340 per server, or one every four minutes on every server, around the clock. Another 960 connections a day probed and hung up without trying a password.
- About 6,400 different addresses in 30 days, 420 on an average day.
- 3,860 different usernames, led by
root: 13,100 attempts from 1,600 addresses.
Nothing about those servers is special, so expect the same on yours. That traffic is automated, constant, and cheap to retarget. A bug that lets one long password open a key-only account is exactly what scanners add next, and with guesses arriving every few minutes, an exposed server won’t stay unfound for long. Treat this as a fix for today, not for your next maintenance window.
alpine was tried only 4 times in those 30 days. We are watching for that to change, and every Warden server that reports to Witen feeds that view.
What Warden 0.1.46 adds: find it first, catch it if it happened
Warden 0.1.46 finds servers exposed to CVE-2026-123158 before an attacker does, and raises the alarm if someone already got in. It doesn’t prevent the attack; updating OpenSSH does.
- The exposure check finds it first.
sudo warden validategains a check,ssh_password_bypass, that reports each server as affected, mitigated, not affected, or unknown, following Alpine’s advisory. It reads which C library sshd uses, its OpenSSH version, and on Alpine the exact package revision against Alpine’s fixed releases. Then it checks whether sshd uses PAM, whether password logins are on, which accounts have no password, and whether another account has a$5$or$6$hash. An affected server fails the check, names the exposed accounts, and shows the commands to fix it. Warden also sends the result to your Witen dashboard, where the exposure page lists it under server configuration checks. - The compromise alert catches it after the fact. A password login accepted for an account with no password or a locked one can’t happen legitimately, so Warden logs it as an error with the account, the address, and what to do, and reports it to the Witen network. It means someone is already in, probably with root. Treat the server as compromised and rebuild it. It alerts on any system, not just musl, because that login is never normal.
- It bans the address on the first login. The attack takes one connection, so there is no second chance to wait for. With firewall blocking on, Warden bans the address immediately; in observation mode it reports without banning. Only a complete sshd “Accepted” line counts, so an attacker can’t plant a fake one in the logs to get someone else banned.
- It cuts the attacker’s session. For this alert only, after the ban Warden also closes every open connection from that address, the same way
ss -Kdoes, so an SSH session already in progress ends at once. Loopback and addresses you’ve told Warden never to block are left alone, and in observation mode it only logs. Closing connections needs a kernel feature, socket destroy, that every standard distribution kernel we checked turns on: Ubuntu's generic kernel, the RHEL family, Debian, and Alpine's LTS kernel. On a custom-built kernel without it, Warden keeps the ban, which already stops the attacker’s traffic, and tells you the command to end the session by hand. That can end an attack early. It doesn’t undo anything the attacker already did, and an attacker with root can stop Warden itself, so the server still needs rebuilding. - Warden stays unprivileged. The sshd settings and
/etc/shadowneed root to read, so a small root helper,prepare-ssh-facts, collects them before Warden starts. It passes on only account names and counts, never a password hash, and if it fails, Warden still starts.
Install Warden or upgrade to 0.1.46. It’s free and needs no account. Then run:
sudo warden validateOn an affected Alpine 3.24 server, the check reads like this:
ssh_password_bypass failed CVE-2026-123158: OpenSSH 10.3p1-r1 on musl is a version with this flaw and password logins are on without PAM. These accounts have no password and are exposed: alpine, root. They accept any password longer than 256 characters (CVE-2026-123158).
hint: Fix it: `sudo apk upgrade -U -a && sudo rc-service sshd restart`. Or turn password logins off: `sudo mkdir -p /etc/ssh/sshd_config.d && echo 'PasswordAuthentication no' | sudo tee /etc/ssh/sshd_config.d/00-no-passwords.conf && sudo sshd -t && sudo rc-service sshd restart` (check with `sudo sshd -T | grep -i passwordauthentication`; an earlier PasswordAuthentication yes in /etc/ssh/sshd_config wins, so change that line too). Details: https://alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/.Once password logins are off, the same server shows as mitigated until you update:
ssh_password_bypass advisory CVE-2026-123158: OpenSSH 10.3p1-r1 on musl is a version with this flaw, but password logins are turned off, so the attack does not work right now.
hint: Fix it: `sudo apk upgrade -U -a && sudo rc-service sshd restart`.Warden doesn’t change your sshd configuration for you. It tells you exactly what is wrong and what to run, and it watches for the break-in while you fix it.
Step 1: Are you affected?
| Your system | Affected? | Why |
|---|---|---|
| Alpine Linux, including cloud images and Docker images with sshd | Yes, until updated | musl, and Alpine’s default openssh-server is built without PAM. openssh-server-pam with UsePAM yes is not affected. |
| OpenWrt with the openssh-server package | Yes, until updated | musl without PAM. The default SSH server on OpenWrt is Dropbear, which is not affected. |
| Buildroot, Yocto, and other musl builds | Likely, if sshd is built without PAM | Check with the commands below. |
| Debian, Ubuntu, RHEL, Fedora, and other glibc distributions | No | glibc’s crypt() doesn’t behave this way, and these distributions check passwords through PAM. |
| Any sshd using PAM, Dropbear, or OpenBSD | No | PAM never hands a `*` password to crypt(), and Dropbear and OpenBSD don’t share the bug. |
On the server, check each condition:
ls /lib/ld-musl-* 2>/dev/null
sudo sshd -T | grep -E '^(usepam|passwordauthentication|permitrootlogin) '
sudo awk -F: '$2 == "*" {print $1}' /etc/shadow- A
/lib/ld-musl-...file means the system runs on musl. - No
usepamline, orusepam no, means sshd doesn’t use PAM. Alpine’s sshd prints nousepamline at all, because it was built without PAM. passwordauthentication yesmeans password logins are on.- The last command lists your
*accounts. On a stock Alpine server that’s onlyroot, which is protected whilepermitrootloginis anything other thanyes. Any other name on that list is reachable.
Then check whether your OpenSSH package already has the fix. On Alpine, sshd -V shows the OpenSSH version but not Alpine’s patch level, so ask the package manager:
apk list -I openssh-serverCompare it with the table in Step 2.
Step 2: Fix it
Update OpenSSH
| Release | Fixed package |
|---|---|
| Alpine edge | openssh-server 10.6_p2-r0 |
| Alpine 3.24 | openssh-server 10.3_p1-r2 |
| Alpine 3.23 | openssh-server 10.2_p1-r1 |
| Alpine 3.22 | openssh-server 10.0_p1-r11 |
| Alpine 3.21 | openssh-server 9.9_p2-r1 |
| Alpine 3.20 | openssh-server 9.7_p1-r6 |
| Alpine 3.19 | openssh-server 9.6_p1-r3 |
| Alpine 3.18 | openssh-server 9.3_p2-r4 |
| Alpine 3.17 | openssh-server 9.1_p1-r7 |
| Alpine 3.16 | openssh-server 9.0_p1-r6 |
| OpenWrt snapshots and 25.12 | openssh-server 10.6_p2-1 |
| OpenWrt 24.10 | No fixed package yet. Turn password logins off. |
| Your own builds | OpenSSH 10.6p2 or later |
On Alpine, update everything, as Alpine’s advisory recommends, then restart sshd:
sudo apk upgrade -U -a
apk list -I openssh-server
sudo rc-service sshd restartRestart sshd after the update so every new login runs the fixed code. Alpine 3.21.10, 3.22.8, 3.23.8, and 3.24.4, released the same day, include the fix for new installs and images.
Can’t update yet? Turn password logins off
If your logins use SSH keys, you don’t need password logins at all. First make sure your key login works, in a second session you keep open, so you can’t lock yourself out. Then:
echo "PasswordAuthentication no" | sudo tee /etc/ssh/sshd_config.d/00-no-passwords.conf
sudo sshd -t && sudo rc-service sshd restart
sudo sshd -T | grep '^passwordauthentication 'The last command should print passwordauthentication no. On a systemd host, restart with sudo systemctl restart sshd instead. sshd uses the first value it reads for each setting. Alpine’s sshd_config includes sshd_config.d near the top, so the drop-in wins over a yes further down. If sshd -T still says yes, a PasswordAuthentication yes line sits above that include: change it to no too.
Adding UsePAM yes to the default package does not fix it. Alpine’s default sshd was built without PAM, so it ignores the setting with an “Unsupported option” warning. Alpine’s separate openssh-server-pam package with UsePAM yes is not affected, but updating is the simpler fix.
Step 3: Check whether someone already got in
A password login accepted for a * or locked account is impossible on a healthy server. If your logs show one, someone used this bug. This prints any such login:
for u in $(sudo awk -F: '$2 == "*" || $2 ~ /^!/ {print $1}' /etc/shadow); do
sudo grep -h "Accepted password for $u " /var/log/messages* 2>/dev/null
doneNo output is what you want. Alpine’s default logger writes sshd’s messages to /var/log/messages, and the line looks like sshd-session[24]: Accepted password for alpine from 203.0.113.7 port 44502 ssh2. If you send logs elsewhere, search there. On a systemd host, use sudo journalctl -t sshd -t sshd-session | grep "Accepted password for" and compare the names with your * accounts.
If you find one, treat the server as fully compromised. An attacker who logged in as a user with root access through doas or sudo can change anything, including the logs. Take the server off the network, keep a copy of its disk if you want to investigate, and rebuild it from a known-good image with the fixed OpenSSH. Rotate every secret the server held: SSH keys, API tokens, database passwords. No output is reassuring but not proof, since logs rotate away and an intruder can erase them.
Containers and images
Most containers don’t run sshd, and those aren’t affected. Check any image that does, especially Alpine-based ones built for remote access, CI runners, or jump hosts:
- Rebuild the image so it pulls the fixed
openssh-server, and redeploy. Updating a running container is lost on the next restart. - Bake
PasswordAuthentication nointo the image if it only uses keys. - Check images you didn’t build. Run the Step 1 commands inside the container with
docker exec.