sshole: The OpenSSH Password Bypass, and Why Debian Is Not in Scope

OpenSSH shipped 10.6p2 yesterday to fix a password bypass that has been dubbed sshole. The headlines say “critical” and “log in without a password”, which is enough to make anyone with an SSH port open reach for a terminal at 6am. I did, and the first question was not “how do I patch” but “am I even in scope”.

This post covers how the bug works, who is exposed, what I found when I checked my own Debian hosts with Ansible, and what to do if you do run an affected system. I am working from the advisories and press coverage. I have not reproduced the bug, and I am not going to.

The short version

If your servers run Debian, Ubuntu or Fedora, this bug does not apply to you. It needs sshd built against the musl C library and checking passwords without PAM. That describes Alpine’s default openssh-server package, and it can also describe OpenWrt, Buildroot and Yocto builds that ship OpenSSH. Debian uses glibc and PAM, so the broken code path is never reached.

I could not load Debian’s own tracker entry for this CVE this morning, so that conclusion rests on the OpenSSH and Alpine statements about glibc and PAM. That is why I also checked the hosts themselves, which is the section further down.

How the bug works

This one is not memory corruption or broken cryptography. It is two old, sensible decisions that turn out to be dangerous together.

Decision one: a username-enumeration fix (OpenSSH, 2016). If sshd rejected unknown users instantly but spent time hashing a password for real ones, an attacker could tell valid usernames apart by response time. The fix for that, tracked as CVE-2016-6210, makes sshd always do a hash comparison. For an account with no password, the stored field is just *, which is too short to contain a salt. So sshd borrows the salt from another account that has a real hash.

Decision two: a defensive change in musl (2012). musl’s crypt() refuses passwords longer than 256 bytes for the $5$ and $6$ hash types. When it refuses, it returns * rather than NULL, because the musl maintainers found that many programs never checked for failure.

Put those together and the failure is easy to see. An attacker sends a password between 257 and 1,024 bytes long (1,024 is sshd’s own limit). musl refuses to hash it and returns *. The stored field for the target account is also *. The two match, and sshd lets the attacker in. It works on the first try, on a single connection.

The bug has existed since the 2016 change. It was reported by Özgün Kültekin of Wakefield, and OpenSSH describes the 10.6p2 change as a workaround, because the root cause lies outside sshd. The fix makes sshd refuse a match when the stored field is shorter than four characters.

Who is exposed

Alpine’s advisory lists four conditions, and all four have to be true:

  • sshd is built without PAM (Alpine’s default openssh-server), or runs openssh-server-pam with UsePAM no.
  • PasswordAuthentication is yes, which is the OpenSSH default.
  • The target account has * as its password field, meaning no password is set.
  • At least one other local account has a password hash starting with $5$ or $6$.

The attacker also needs the name of an account with no password. Accounts like that are common: key-only users, and root, which ships as root:* in the Alpine base image. Root is protected by the default PermitRootLogin prohibit-password, but that does not help if an exposed account can become root, for instance through doas.

Not affected: anything running glibc, anything that checks passwords through PAM, systems with PasswordAuthentication no, accounts locked with !, and containers that do not run sshd. OpenWrt uses Dropbear by default, and Dropbear is not affected, so OpenWrt only matters if someone installed the OpenSSH server on it. No exploitation in the wild had been reported when the advisories were published, although public reproduction labs already exist.

Checking a Debian fleet with Ansible

My Debian group has four hosts, so on paper I am out of scope. This check turns “on paper” into evidence. It reports the distribution, the C library and the two sshd settings that matter, for every host in the group:

cd ~/homelab-ansible
ansible debian -b -K -m shell -a "grep ^PRETTY_NAME /etc/os-release; ldd --version 2>&1 | head -1; /usr/sbin/sshd -T | grep -E '^(usepam|passwordauthentication) '"

Run it from inside your Ansible project directory, so Ansible picks up your ansible.cfg and inventory. -b -K is needed because sshd -T has to run as root.

What I got back, for all four hosts:

  • Debian 13 (trixie).
  • ldd reports Debian’s glibc. A musl host would print a line starting with musl libc here, and that is the one you would need to look at.
  • usepam yes.

That is the combination that keeps them out of this bug. passwordauthentication was yes on two hosts and no on the other two. With PAM handling the password check, that setting does not matter for sshole. It is a useful reminder, though, that “is PAM on” and “are passwords on” are separate questions, and I will be reading the other two settings more carefully now.

sshd -T prints the configuration sshd is actually using, after defaults and Include files are applied, which makes it more reliable than reading sshd_config by eye.

Should you run the update playbook anyway?

Yes, but for routine reasons rather than because of this bug. This CVE does not touch Debian, so there is no emergency, and a normal upgrade run keeps you current for the next one.

I used the playbook from my last upgrade post, which does a dry run by default. The dry run listed a handful of routine package updates on three of the four hosts and none on the fourth. None of the lists contained an OpenSSH package. That fits Debian not needing a fix for a bug it is not affected by, although it only tells you what Debian’s repositories offered at that moment, not what Debian’s security team may publish later. I then applied the upgrades. The playbook also reported pending reboots on some hosts, for kernel updates unrelated to this bug. It reports them and never reboots by itself, which is the point of it.

If you go the same way, do the dry run first and read the list of upgradable packages before you apply anything. For Debian’s own view, the Debian security tracker for the openssh source package is where a Debian-specific advisory would appear.

If you do run Alpine

The fix is a package update and a restart, and the restart is the part people forget, because upgrading the package does not restart the running sshd:

apk upgrade -U -a
rc-service sshd restart

Alpine has fixed packages for every release back to 3.16, including branches that are out of support. If you cannot update yet, turning off password logins also stops the attack. Confirm key login works first and keep your current session open until a new login succeeds. Then set PasswordAuthentication no, check it with sshd -t, restart sshd, and confirm with sshd -T | grep -i passwordauthentication.

To check whether you were hit, Alpine’s advisory suggests looking in /var/log/messages for sshd lines saying Accepted password for an account that has no password. The advisory’s advice is blunt: if you cannot rule out a login, treat the machine as compromised.

Container images are worth a glance too. Cycode lists several widely used images that turn password logins off by default, which keeps them out of range, but anything you built yourself on an Alpine base with sshd and password logins on is worth a look.

The part that interests me: the disclosure

The bug was found with AI agents that ran about 17,600 authentication tests across different OpenSSH configurations, malformed password hashes and oversized passwords. It is the same direction as the AI-assisted Linux TCP bug I covered in One Bug, Four Scores: machines are very good at grinding through unusual combinations that no human would bother to test.

The timeline is the other notable thing. The fix shipped within a day of the report. OpenSSH’s Damien Miller said the team now regards long embargoes as risky, because AI tools have recently rediscovered reported bugs, sometimes within hours. Cycode draws the practical conclusion: plan to patch OpenSSH within hours of a release, not days.

One loose end on identifiers. I have used CVE-2026-123158 because that is what Cycode, OX Security and IONIX call it, and the two that publish a score put it at 8.1 High, while OpenSSH itself calls the flaw critical. The Hacker News said no CVE had been assigned when it published. Check the NVD record before you paste an ID into a ticket.

Takeaways

  • Scope first, patch second. “Does it affect me?” took one Ansible command, and most Debian estates are not affected.
  • The bug needs musl, no PAM, password logins on, an account with no password, and one other account with a $5$ or $6$ hash. Remove any one of those and it does not work.
  • On Alpine, restart sshd after the upgrade, or you are still running the vulnerable process.
  • PasswordAuthentication no is the stopgap, and it is a good default anyway. My SSH hardening post goes further.
  • A dry-run-first upgrade playbook doubles as a free check. No OpenSSH package in any upgrade list is what I expected on Debian, and it is reassuring to see it.
  • Two old, individually reasonable changes combined into a decade-old bug. Defensive code is not automatically safe once other components start depending on its behaviour.

Sources: The Hacker News, BleepingComputer, Cycode via Security Boulevard, IONIX via Security Boulevard, OX Security, Alpine Linux advisory, Debian Security Tracker: openssh.