
sshole documents and detects a long-lived OpenSSH-on-musl authentication bypass, shipping a check.sh audit script, a Docker lab, and the upstream patch for defenders.
| Tool | oz9un/sshole — detection script, Docker PoC, and patch for the OpenSSH-on-musl password authentication bypass (2016–2026) |
| Category | Vulnerability research / SSH audit tooling (Shell) |
| Primary Use | Auditing your own Alpine/OpenWrt/Yocto-style systems with check.sh to see whether the musl + non-PAM + *-password conditions coincide |
| Safe Use | Authorized defensive auditing of systems you own or administer; patch verification and lab reproduction in local Docker containers only |
| Telemetry Note | The flaw manifests as successful password logins to accounts whose shadow field is *; defenders should watch for authentications to key-only/no-password accounts and anomalous sshd password successes on musl hosts, and check sshd build for PAM |
sshole is the repository accompanying the disclosure of a decade-old authentication flaw in OpenSSH when it runs on musl-based Linux distributions: any account whose shadow password field is exactly * — traditionally meaning "no password login possible" — could be logged into with any password longer than 256 characters. The flaw affects stock Alpine, OpenWrt, Buildroot, Yocto, postmarketOS, void-musl, and Chimera style systems, including root, key-only users, LDAP users, and the default alpine user on cloud images. The author states it has been exploitable since 2016, and it was fixed upstream in OpenSSH 10.6p2 on 2026-11-05. For an educational and documentary analysis aimed at authorized professionals, this repo is a model of compact disclosure: a self-audit script, a contained lab, and the fix.
The core value for defenders is check.sh, a single self-contained Shell script that inspects a host and prints exactly which of the five necessary conditions for exposure are present. Its output enumerates the libc in use, the sshd build string, whether PAM is compiled in, the effective PasswordAuthentication setting, which accounts carry a * password field, and whether any account has a real $5$ or $6$ sha-crypt hash. If every condition matches it prints EXPOSED with the affected account names and the remediation — upgrade openssh to 10.6p2 via apk, or disable password authentication as an interim. If a condition is missing, the script tells you which one, which is exactly the triage behavior you want in an audit tool.
The vulnerability is a convergent logic flaw rather than a memory-corruption bug, and understanding it is the interesting part. sshd has, since the 1990s, compared the crypt() output against the stored password field using strcmp(). For accounts with *, the stored field is too short to parse as a salt, so since a 2016-era change sshd borrows another account's salt to build the comparison. Meanwhile musl's crypt() — since 2012 — returns the literal string * on failure, and it fails for keys over 256 bytes. Feed a 257+ character password, and the failure sentinel * compares equal to the stored *: authentication succeeds. The README compresses this into a six-line annotated trace that is genuinely useful for teaching how sentinel-value collisions between libc and a privileged daemon produce a bypass.
Exposure requires all five conditions simultaneously, and this conjunction is why the bug survived a decade. The system must use musl; sshd must be built without PAM (Alpine's default openssh-server package is, while openssh-server-pam with UsePAM yes is safe); sshd_config must have PasswordAuthentication yes (the default); at least one account must have exactly * as its password field; and at least one account must have a real $5$ or $6$ hash — meaning someone, at some point, ran passwd on the box. That last condition is the subtle one: a salt must exist to be borrowed. Systems on glibc, with PAM, with password auth disabled, or on OpenBSD/FreeBSD/macOS are explicitly not affected.
The repository deliberately keeps the reproduction contained: a poc/ directory with a Dockerfile that builds a stock Alpine 3.24 with default openssh-server, where alice has a * field and bob has a real password. The published attack.py uses paramiko to demonstrate the boundary condition — a 256-character password is rejected while 257 succeeds — and the author notes the flaw reproduces at least back to OpenSSH 7.5p1 on musl 1.1.16 from 2017, with a BASE build argument to test older Alpine releases. We won't reproduce operational attack sequences here; the point for professionals is that the flaw is deterministic, requires no brute force, and the boundary at 256/257 characters is a clean signature.
Detection and forensics angles follow directly from the mechanics. Because the bypass is a *successful password authentication* to an account that should never accept passwords, defenders should review authentication logs on musl hosts for password-based logins to key-only or *-password accounts — those are inherently anomalous. The root variant additionally requires PermitRootLogin yes, so that configuration on an Alpine cloud image deserves immediate scrutiny. Since the check is purely local and read-only against /etc/shadow-derived state and sshd build flags, running check.sh across a fleet leaves no meaningful footprint beyond normal script execution.
Remediation guidance in the README is layered and pragmatic. The primary fix is upgrading to openssh 10.6p2 — Alpine packages were already available at publication time. For systems that cannot be updated immediately, setting PasswordAuthentication no in sshd_config "closes it completely," which is a strong, verifiable compensating control that also fits key-only operational models. For embedders pinning older OpenSSH, the repo carries patch/4353fdc43-avoid-password-authn-bypass.patch, described as the unchanged upstream fix, applicable to anything from 7.3p1 upward; it makes sshd skip the comparison when the stored field is too short to be a real hash and removes reliance on a pointer into libc's static buffer.
The project's structure reflects a deliberate disclosure philosophy: the repo is "small on purpose," with the full 17-environment test matrix (cloud images in qemu, OpenWrt, Buildroot, LDAP and AD users), prevalence numbers, and per-distro table held back in the accompanying write-up at sshhole.com, possibly to be published later. A TIMELINE.md traces the three converging changes: sshd comparing against * since the 90s, musl returning * on crypt() failure since 2012, and sshd borrowing salts in 2016. The repo carries an MIT license, is written in Shell, and sits at 178 stars at the time of this analysis.
Where does sshole fit in an authorized workflow? It is a fleet-audit and patch-verification utility, not an attack tool. Run check.sh on your Alpine routers, containers, Yocto-built appliances, and cloud instances to enumerate exposed accounts; confirm after the 10.6p2 upgrade that the script now reports the fixed sshd version; and use the Docker lab to validate detection logic — say, sshd log-alerting rules keyed to password authentications for *-field accounts — before deploying it. The boundary-length behavior (256 fails, 257 succeeds) also gives blue teams a crisp unit test for any authentication-anomaly analytics.
The broader lesson sshole teaches is about sentinel-value contracts across library boundaries. Two independent, individually reasonable design decisions — musl returning * as a failure sentinel, and sshd reusing salts for unparseable fields — combined into an authentication bypass that survived three OpenSSH major versions and a decade of deployment on some of the most security-conscious minimal distributions. For anyone building hardened images on musl, the takeaway is concrete: prefer PAM-built sshd where feasible, avoid * password fields in favor of ! or locked semantics your tooling explicitly verifies, and treat "impossible" login paths as things to test, not assume. This writeup is documentary analysis of a published, already-patched flaw, intended for defenders and authorized security teams validating their exposure.
oz9un/sshole.Educational analysis for authorized security professionals. Use only in controlled, authorized environments.
Related coverage
0 comentários:
Post a Comment
Note: Only a member of this blog may post a comment.