Can I Detect sshole?
sshole lets anyone log into a passwordless account on a musl + no-PAM OpenSSH host with a 257-byte password. We detonated it in our lab to map the attack across 6 procedures and more than 15 detectors and log sources, and measure real detection coverage for this threat.

How sshole works
On the one platform combination that mishandles long passwords — musl libc with PAM disabled, which in practice means Alpine and the embedded distros built on it — OpenSSH lets anyone log into a passwordless account. musl's crypt() returns the string * when a password runs past 256 bytes, and * is exactly what the shadow file stores for an account that has no password, so a password of 257 bytes or more matches (CVE-2026-123158, CVSS 8.1). One wrinkle shapes every detection below: on a stock Alpine build the first attempt in a connection fails and the second succeeds — a salt-cache quirk — so a successful bypass always leaves a Failed password immediately followed by an Accepted password for the same account. That reaches any account whose shadow field is * — key-only and service users — and because the official Alpine cloud images ship an alpine user with passwordless doas, on those hosts the bypass becomes root. glibc systems, PAM-enabled sshd, Dropbear and OpenBSD are not affected. The upstream fix is OpenSSH 10.6p2; on Alpine it arrives as the backported openssh-server 10.3_p1-r2.
How we tested it
We stood up the real vulnerable stack in our lab — the official Alpine/musl image the bug ships on, with a glibc host alongside to confirm the bug really is musl-specific — and detonated the attack dozens of times under a broad array of logging mechanisms and detectors: auditd, rsyslog, Tetragon, bpftrace, process accounting, sysstat, tcpdump and Suricata on the wire. We also assessed the two mainstream agents that run on musl — Wazuh (by configuration, re-checked against a fresh bypass) and Falco (containerized, since its native engine wouldn't start on our kernel). The point was to see exactly what each step produces, what telemetry we could actually collect on this platform, and which of it is usable to detect the attack. Every procedure marked lab-verified in the map below was captured on-box; the map is the result.
The 6 procedures, and the 10 shipped detections and tools that touch them:
Network
01Spray: root and alpine usernames, long passwordsLab-verifiedRapid password failures to root or alpine from one sourceT1110.00311 detection
Detection logic
Rapid failures to root/alpine from one source. Provenance: this is single-source — one honeypot (Beaumont) reported root/alpine long-password spraying, with no IOCs published and no other outlet confirming it, so treat it as a weak, unconfirmed signal. Background noise on its own; a risk contributor until paired with a bypass-login success. Lab: OpenSSH's own PerSourcePenalties emits free telemetry after about four rapid failures, from the listener (sshd), not sshd-session. Locked accounts log as 'invalid user' plus a NOUSER shadow error, and dropped connections never reach the banner, so a scanner sees an EOF/reset rather than a clean refusal.
sshd[LISTENER]: srclimit_penalise: <src-ip>/32: activating ipv4 penalty of 18.777s sshd[LISTENER]: drop connection #0 from [<src-ip>]:<port> penalty: failed authenticationLab ✓ sshd penalty linesLab ✓ SuricataLab ✓ tcpdumpLab ✓ NetFlow / VPC Flow
Logs it needs
- SSH auth logs (penalty lines)on by defaultsrcip, user, srclimit_penalise, drop connection
- NetFlow / VPC Flow Logsneeds setup5-tuple, bytes, ACCEPT/REJECT
Shipped detections · 1
- Generic SSH brute-force / failed-auth volumeSplunk ES, Sentinel, Wazuh (stock content)Genericvendor-stated
Misses: Keys on failure volume, not the bypass; throttled attackers stay under threshold; nothing correlates the spray to the success.
Make it a 10–20 point risk contributor keyed on source, and fire on correlation with a bypass-login success.
02Over-length password on the wireLab-verifiedThe password is encrypted but its size leaks into the userauth packet lengthT1190 (network view)No rule · build itNo rule · build it
Detection logic
The password is encrypted, but its size leaks. Lab: a 300-byte password shows as two ~400-byte client-to-server packets during userauth, against 48–80 bytes for normal password auth. Suricata records the client software version (e.g. paramiko for a scripted attacker). Note the false-positive floor from our evasion tests: Ed25519 pubkey auth is ~256 B and RSA pubkey overlaps 350–500 B, so packet size is corroborating-only on mixed-auth fleets, clean on password-only listeners, and unambiguous past ~1100 B (a 1024-byte password).
ssh userauth: client→server packet length = 400 B (normal password auth: 48–80 B)Lab ✓ tcpdump (400 B)Lab ✓ Suricata (client version)
Logs it needs
- pcap / NDR userauth phaseneeds setupclient→server packet length during auth
- Suricata eve.json sshneeds setupsoftware_version, client/server
Shipped detections · 0
No public rule uses this. Build an NDR heuristic: SSH userauth client→server packet of roughly 350 bytes or more. Treat the tool fingerprint (paramiko) as a weak corroborator.
Identity
03Bypass login to a passwordless (*) accountLab-verifiedA successful password login for an account that cannot hold a passwordT1190 + T1078.00344 detections
Detection logic
The invariant: a successful password login for an account whose shadow field is *. Lab ground truth: always a Failed password then Accepted password pair, same user, same client port, same sshd-session PID, 2–7 ms apart, 12 of 12 trials (see the post for why the first attempt fails). The precise analytic joins the Accepted event to the host's *-account list; the no-inventory analytic keys on the pair. Never filter on password length: sshd never logs it. On non-PAM Alpine the audit loginuid is unset (4294967295), so correlate by PID and time, not loginuid.
sshd-session[PID]: Failed password for alpine from <src-ip> port <p> ssh2 sshd-session[PID]: Accepted password for alpine from <src-ip> port <p> ssh2 # +4 ms, same PID and portLab ✓ rsyslog (Failed→Accepted)Lab ✓ sshd logLab ✓ TetragonLab ✓ auditd
Logs it needs
- /var/log/messages (busybox syslogd)on by defaultAccepted password for <user> from <ip> port <port>
- Account inventory (*-shadow)needs setupawk -F: '$2=="*"' /etc/shadow, per host
Shipped detections · 4
- Rule 5715 — sshd authentication success
Misses: Verified running on Alpine (vendor repo): logcollector analyzes /var/log/messages by default, so this rule's telemetry reaches the SIEM out of the box. But it fires on every password login and is blind to shadow state (needs a *-account allowlist join), and logcollector sits downstream of rsyslog, which the attacker's rc-service rsyslog stop blinded until rsyslog restarted.
- Successful SSH Login from Password-Disabled Account (Sigma)
Misses: Despite the title it is just Message contains 'Accepted password', with no account-has-* condition. Usable only with the allowlist added.
- OpenSSH Sshole Authentication Bypass Attempt - Oversized Password (Sigma)
Misses: Has no length check at all: it selects 'Failed password' lines and then excludes any message containing 'password', filtering out its own selection. Cannot fire. Don't deploy.
- sshole-audit host and fleet probe
Misses: An audit tool, not alerting: a point-in-time grep of auth logs for exactly this invariant.
The account-join analytic is near-zero false positive. For the connection-shape analytic, gate on the same user + source port + sshd-session PID, not the millisecond gap: sshd refuses a mid-connection username change, so the pair is daemon-guaranteed same-user, and our evasion runs still succeeded with a 10-second gap (LoginGraceTime, ~120 s, is the ceiling). Treat the sub-second timing as a score booster, not the gate.
Endpoint
04Privilege escalation via passwordless doasLab-verifiedThe tiny-cloud alpine user holds passwordless doas, so a bypass login becomes rootT1548.00333 detections
Detection logic
The tiny-cloud alpine user holds passwordless doas, so a bypass login becomes root. Lab: the Alpine doas log format is verified; Tetragon also shows the uid 1000 to 0 transition inside the same PID.
doas: alpine ran command sh -c '...useradd...' as root from /home/alpine # newlines render as #012Lab ✓ rsyslog (doas authpriv)Lab ✓ Tetragon (uid 1000→0)Lab ✓ auditdLab ✓ acct
Logs it needs
- doas syslog (authpriv)on by default<user> ran command … as root from <cwd>
Shipped detections · 3
- Linux Doas Tool Execution
Misses: Requires Sysmon for Linux (sourcetype sysmon:linux, Endpoint data model), which is glibc-only — on Alpine, use the auditd twin below instead. Doesn't correlate to the SSH session on its own.
- Linux Auditd Doas Tool Execution
Misses: The Alpine-viable twin of the rule above: keys on auditd type=SYSCALL comm=doas, and the lab confirmed auditd runs on musl. Fires on every doas, so it needs correlation to the preceding bypass-login session to mean compromise.
- doas / sudo-caching abuse patternsElastic, Wazuh (stock content)Genericvendor-stated
Misses: Not in our lab; doas nolog suppresses the success line; the Alpine format is now verified, so these can be adapted.
Alert when the escalating user is alpine or any key-only account and the preceding login was a bypass-login hit.
Exposure
05Tiny-cloud alpine user exposureThe official Alpine cloud images (AWS and Azure) ship alpine:* with passwordless doas — the exposure that makes the bypass rootExposureNo rule · build itNo rule · build it
Detection logic
The official tiny-cloud images (3.18.11 through 3.24.3, per the Alpine advisory) ship alpine:* with passwordless doas: safe on first boot, but the moment any other account holds a $5$ or $6$ hash (an admin console password, a user-data hashed_passwd) the bypass reaches this user, and its passwordless doas makes it root — the escalation the doas procedure shows. This row is an exposure to census for, not an on-box detection.
Logs it needs
- EC2 / AMI inventoryneeds setupimage id, openssh version, account types
Shipped detections · 0
Census AMIs and instances for musl + openssh-server; treat "tiny-cloud image + any $5$/$6$ account" as exploitable and prioritise.
06Unpatched musl + no-PAM sshdLab-verifiedThe control check: is the host still vulnerable, and is the running daemon the patched one?Prevent22 detections
Detection logic
The effective control is upstream. Lab: post-patch (openssh-server 10.3_p1-r2) every payload failed on every attempt and legitimate auth still worked; the glibc control (Ubuntu 24.04) and a musl + PAM build both refused the bypass on every path. Two traps, both confirmed on-box: (1) apk upgrade openssh upgrades only the meta-package — openssh-server stays at the vulnerable r1, its sshd binary hash unchanged, and the bypass still works; only apk upgrade -U -a fixes the server subpackage. (2) The upgrade does not replace the running daemon until rc-service sshd restart, so compare /proc/$(pidof sshd)/exe with the on-disk binary rather than trusting the upgrade event.
Lab ✓ lab patch validation (post-fix: all payloads fail)Lab ✓ glibc + musl-PAM negative controls (all fail)Logs it needs
- apk package inventoryneeds setupopenssh-server (not the openssh meta-package) version against the fixed table
- sshd -T + /proc/$(pidof sshd)/exeneeds setupUsePAM, PasswordAuthentication; running vs. on-disk binary
Shipped detections · 2
- sshole-audit / check.sh exposure probes
Misses: Point-in-time probes, not continuous; vendor backports on non-Alpine musl are invisible in the version string.
- VM coverage for CVE-2026-123158VM scanners (generic)ssholevendor-stated
Misses: Remote probing can't tell vulnerable from merely exposed, and one-shot probes miss attempt-2 hosts; detect the vulnerable package still installed.
Alert on the vulnerable package still installed (apk list -I | grep openssh-server not at the fixed version), not on the upgrade event — the meta-package trap means an apparently-upgraded host can still be vulnerable.
Scope is the headline: this is musl + no-PAM only, so glibc hosts (Debian, Ubuntu, RHEL, SUSE, Arch) are not affected and don't belong in the count. The map is also scoped to the attack itself — the bypass, the documented root-via-doas path it enables, and the exposure that gates it — not the post-compromise actions (persistence, mining, anti-forensics) an intruder could run afterward, none of which anyone has tied to sshole. Even the spray that precedes the bypass rests on a single honeypot report, so weight it lightly. Within that scope the picture splits cleanly: the exposure and patch checks have decent tooling, but the bypass login itself — the clearest signal in the map, the sub-second Failed-then-Accepted pair — has no shipped rule that keys on it correctly. The one Sigma rule written for exactly this is logic-flawed and can't fire; the usable community rule needs a *-account allowlist bolted on first; and the spray is covered only by generic brute-force rules that predate the threat. Two of the six procedures have no shipped detection at all.
The full research behind this map is open source as the sshole Delta Tradecraft pack — one portable object per procedure, with the detection logic, the logs it needs, and what each shipped rule misses. Delta Tradecraft is our format for detection knowledge that travels between tools, so you can build the missing detections on whatever stack you run.
Want to know which of these 6 you'd catch today? Connect Spectrum and see your own coverage.




