URGENT SECURITY ADVISORY (P0): A high-severity authentication bypass vulnerability in OpenSSH—tracked as CVE-2026-123158 and universally named SSHole (stylized as *sshole)—was disclosed on November 5, 2026 by security researcher Özgün Kültekin. The flaw enables unauthenticated remote attackers to gain instant, passwordless shell access to password-disabled accounts (marked with an asterisk * in /etc/shadow) simply by submitting a password between 257 and 1,024 characters in length. Affected systems run OpenSSH on distributions using the musl C library without PAM (most notably Alpine Linux, OpenWrt, Buildroot, and Yocto). Patch OpenSSH to 10.6p2 or disable password authentication immediately.
In the discipline of systems software security, the most elusive vulnerabilities are rarely single catastrophic programming blunders. Instead, they are emergent boundary failures—accidental catastrophes born from the collision of two individually well-intentioned, decade-old engineering decisions written by separate teams with conflicting threat models.
CVE-2026-123158 is a textbook masterclass in this failure mode. For roughly ten years, a timing-attack mitigation added to OpenSSH in 2016 quietly coexisted with a denial-of-service defense inside the lightweight musl C library. Each mechanism worked exactly as intended within its own abstraction boundary. But when connected together on a system where Pluggable Authentication Modules (PAM) are disabled, they open an astonishing remote authentication hole that allows an attacker to log straight into key-only or password-locked accounts without ever knowing a secret.
In this technical report, the Lorikeet Security Threat Research Team breaks down the anatomy of SSHole: the C source code interaction across OpenSSH and musl, the Unix shadow file star invariant, the high-risk reality of Alpine Linux cloud images, SIEM detection rules, and the complete remediation playbook for production engineering teams.
The Root Cause Triad: How Three Decades of Unix Standards Collided
To understand why SSHole exists, one must examine three completely separate software behaviors that have existed in production Linux environments since at least 2016:
| System Component | Original Design Intent | Actual Behavioral Quirk | Role in the Exploit |
|---|---|---|---|
| OpenSSH Daemon (CVE-2016-6210 Mitigation) | Prevent remote username enumeration via side-channel timing leaks | Borrows a valid password salt from another local account and calls crypt() even when target account is locked |
Supplies a valid salt to the hash function for accounts that should never be hashed |
musl libc (crypt() Engine) |
Protect systems against algorithmic CPU exhaustion from gigantic passwords | Truncates passwords at 256 bytes and returns literal sentinel string "*" on key-length error |
Produces a deterministic output string that matches Unix locked-account syntax |
Unix /etc/shadow Specification |
Mark system, deployment, and key-only accounts as non-loginable via passwords | Stores a single asterisk "*" in the password hash field |
Becomes the target string compared against musl's error return value |
1. The 2016 OpenSSH Timing Attack Defense
In 2016, researchers discovered that OpenSSH leaked whether a requested username existed on the target server (tracked as CVE-2016-6210). When an attacker requested authentication for a non-existent user, OpenSSH responded almost instantly. But when an attacker requested authentication for a legitimate user, sshd spent tens or hundreds of milliseconds executing computational password-hashing algorithms (such as bcrypt, sha512crypt, or yescrypt).
To close this timing oracle, OpenSSH introduced dummy hashing logic in auth-passwd.c. If an account did not have a valid, usable password hash—such as an invalid user, or an account with a disabled password field—OpenSSH would still run crypt() against a fake password or borrowed salt, ensuring the response latency matched that of a real account verification.
Specifically, OpenSSH maintains a routine that inspects local shadow records. When an account has no password hash, OpenSSH borrows the salt format and settings from another legitimate account on the system (typically root or an administrative user with a valid $6$ SHA-512 hash), ensuring that crypt() computes a realistic hash cost.
2. musl libc's 256-Byte Key Length Truncation
The musl C library is the bedrock of lightweight, security-hardened Linux distributions, powering millions of Docker containers, Kubernetes worker nodes, Alpine Linux servers, and embedded firmware (OpenWrt, Yocto, Buildroot). One of musl's core design tenets is resource exhaustion resilience.
Because hashing massive strings with password algorithms can tie up the CPU and lead to denial-of-service (DoS), musl's crypt() implementation enforces an explicit sanity check: the submitted plaintext password key must not exceed 256 bytes. If a caller passes a password longer than 256 bytes, musl considers this an invalid key and immediately terminates computation.
Here is where the flaw emerges. How does musl report this failure to the caller? Under standard POSIX error handling, one might expect crypt() to return NULL and set errno = EINVAL. However, to prevent legacy callers from crashing on null pointer dereferences, musl chose to return an invalid salt sentinel string: a single literal asterisk: "*".
3. The Unix Shadow Star Convention
For more than thirty years, Unix system administrators and user management utilities (such as useradd, usermod -L, and passwd -l) have utilized a single asterisk "*" in the second field of /etc/shadow to represent an account that has no password set or whose password authentication is disabled:
alpine:*:19842:0:99999:7:::
deployer:*:19842:0:99999:7:::
git:*:19842:0:99999:7:::
The assumption across Unix history was universal: because standard cryptographic hash functions output format strings beginning with identifiers like $1$ (MD5), $5$ (SHA-256), $6$ (SHA-512), or $y$ (yescrypt), no legitimate password entered by a human could ever hash to the literal string "*". Therefore, an account with "*" in its shadow entry was considered mathematically impossible to authenticate via password.
The Exploit Chain: Step-by-Step C Source Code Trace
When these three components interact on a Linux system running OpenSSH without PAM, the security barrier vanishes entirely. Here is the step-by-step trace of what happens inside the sshd process memory during an exploitation attempt:
The OpenSSH Verification Code Path
In standard OpenSSH when compiled without PAM (or when UsePAM no is configured), password verification is handled by sys_auth_passwd() inside auth-passwd.c. The simplified logic flows as follows:
/* Inside sys_auth_passwd() in auth-passwd.c */
int
sys_auth_passwd(struct ssh *ssh, const char *password)
{
struct passwd *pw = authctxt->pw;
char *encrypted_password;
/* If account has disabled or empty password, shadow string is "*" */
char *shadow_password = get_shadow_passwd(pw->pw_name);
/* Borrow salt to normalize hashing time against user enumeration */
char *salt = shadow_password;
if (shadow_password[0] == '*' || shadow_password[0] == '!') {
salt = get_dummy_or_borrowed_salt(); /* Borrows salt from another valid account */
}
/* Call the C library crypt() implementation */
encrypted_password = crypt(password, salt);
/* Direct string comparison between crypt result and shadow entry */
if (encrypted_password != NULL &&
strcmp(encrypted_password, shadow_password) == 0) {
return 1; /* AUTHENTICATION GRANTED! */
}
return 0; /* Authentication failed */
}
Notice the deadly vulnerability at line 20: strcmp(encrypted_password, shadow_password). If the target user's shadow entry is "*", and crypt() returns "*", strcmp() compares two identical pointers and returns 0 (match). OpenSSH concludes that the user successfully entered the correct password!
Why the Password Must Be Between 257 and 1,024 Characters
The mathematical boundaries of this exploit are tightly constrained by both sides of the interface:
- Minimum Length (257 bytes): musl libc's
crypt()permits passwords up to 256 bytes. If an attacker submits a password of 256 bytes or fewer, musl performs standard hashing, returning a valid SHA-512 string (e.g.,$6$...), which will not match"*". The password must be at least 257 bytes to trigger the key truncation error. - Maximum Length (1,024 bytes): OpenSSH enforces its own internal protocol buffer limit on authentication credentials. In OpenSSH, the password buffer submitted over the wire via
SSH_MSG_USERAUTH_REQUESTis capped at 1,024 bytes. Anything larger causes OpenSSH to drop the connection or truncate beforecrypt().
Therefore, any arbitrary payload containing 257 to 1,024 characters triggers the exact flaw condition.
The Blast Radius: Why Alpine Linux Cloud Images Are Severely Exposed
While the flaw technically exists in any OpenSSH installation running on musl libc without PAM, the practical real-world blast radius centers directly on Alpine Linux.
Alpine Linux is the standard operating system for containerized microservices and lightweight cloud servers. However, two default configuration choices turn SSHole into a remote root compromise on Alpine cloud instances:
# 1. /etc/shadow contains a default "alpine" user with no password:
alpine:*:19842:0:99999:7:::
# 2. /etc/doas.conf grants the "alpine" user passwordless root privileges:
permit nopass :wheel
# (alpine user is in the wheel group)
# 3. Default Alpine OpenSSH package is compiled WITHOUT PAM:
# /etc/ssh/sshd_config has:
PasswordAuthentication yes
The "Sleeping" Activation Trigger
On pristine first boot, an Alpine cloud instance is safe from remote exploitation for one subtle reason: there are no other accounts on the system with valid password hashes for OpenSSH to borrow a salt from! If OpenSSH cannot find a legitimate salt (such as a $6$ hash on root), its salt-borrowing fallback does not trigger musl's SHA-512 engine in the specific sequence required.
However, the moment an administrator or automation script performs standard administrative housekeeping—such as setting a local console password for root or creating an administrative account with a password—the trap springs shut. The presence of that single valid password hash provides OpenSSH with the salt it needs to borrow. Immediately, the alpine user account becomes remotely accessible to any attacker on the internet via SSHole.
Once logged in as alpine, the attacker runs doas -s (which requires no password under Alpine's default cloud image rules) and instantly gains full, unconstrained root control of the node.
Exploitation Prerequisites Matrix: Is Your Infrastructure Vulnerable?
For an environment to be vulnerable to CVE-2026-123158, all five of the following conditions must be simultaneously met:
| Prerequisite Criterion | Vulnerable State | Safe State | Technical Rationale |
|---|---|---|---|
| C Standard Library | musl libc (Alpine, OpenWrt, Yocto, Buildroot) |
glibc (Ubuntu, Debian, RHEL, CentOS, Arch) |
Only musl's crypt() returns "*" on key-length overflow; glibc handles errors differently. |
| OpenSSH PAM Configuration | UsePAM no (or compiled without PAM support) |
UsePAM yes |
When PAM is enabled, authentication delegates to pam_unix.so, which enforces independent shadow validations. |
| SSH Password Authentication | PasswordAuthentication yes |
PasswordAuthentication no |
If password authentication is disabled, OpenSSH rejects password packets before calling crypt(). |
| Target Account Shadow State | Password field set to * in /etc/shadow |
Password field set to !, !*, or valid hash |
musl returns literal "*", which only matches accounts with bare asterisks. |
| Secondary Helper Hash | At least one local account on the machine has a valid password hash | Zero local accounts possess password hashes | OpenSSH requires a real salt to borrow for its timing-attack normalization routine. |
Audit & Verification: How to Test Your Systems Safely
Security researcher Özgün Kültekin released an open-source audit utility, check.sh, within the oz9un/sshole repository. The script performs non-destructive local introspection to verify whether a host satisfies the vulnerability criteria:
# 1. Download and run the official audit script:
curl -sO https://raw.githubusercontent.com/oz9un/sshole/main/check.sh
sudo sh check.sh
# 2. Or perform manual verification in 3 commands:
# Check C Library:
ldd /usr/sbin/sshd | grep musl
# Check SSH Password Authentication status:
sshd -T | grep -E 'passwordauthentication|usepam'
# Check for target star-locked accounts in /etc/shadow:
sudo awk -F: '$2 == "*" {print $1}' /etc/shadow
If ldd confirms musl, passwordauthentication reports yes, usepam reports no, and /etc/shadow reveals accounts with bare *, the system is exposed and must be remediated immediately.
Detection Engineering & SIEM Telemetry
Even before patches can be deployed across large Kubernetes fleets or cloud images, detection engineering teams can identify active exploitation attempts across network and host telemetry.
1. Host-Level Syslog Telemetry (sshd)
When an attacker executes SSHole, OpenSSH logs a successful password login for an account that was supposed to be key-only or locked. Security Operations Centers (SOCs) should immediately alert on any password-based authentication event for accounts with no business reason to use passwords:
index=linux_auth process="sshd" "Accepted password for"
| search user IN ("alpine", "deploy", "git", "jenkins", "ansible", "ubuntu")
| stats count by host, user, src_ip, timestamp
2. Auditd Rule: Monitoring Shadow Access and Execution
Deploy the following Linux auditd rules to track suspicious process trees where an SSH daemon spawns an interactive shell for default cloud users:
# Log all shadow file access by unprivileged users
-w /etc/shadow -p rwa -k shadow_access
# Alert on sshd spawning shells under the alpine user
-a always,exit -F arch=b64 -S execve -F ppid=sshd_pid -F euid=1000 -k sshole_exploit
3. Falco Rule for Kubernetes & Docker Clusters
For organizations operating Alpine containers with exposed SSH daemons (such as legacy SFTP containers or debug sidecars), the following Falco rule flags password logins on Alpine users:
- rule: OpenSSH SSHole Authentication Bypass Attempt
desc: Detects interactive shell execution under password-disabled accounts spawned by sshd
condition: >
spawned_process and
proc.pname = "sshd" and
user.name in ("alpine", "guest", "nobody") and
container.id != "host"
output: "CRITICAL: Potential SSHole Exploit Detected (user=%user.name container=%container.name command=%proc.cmdline ppid=%proc.ppid)"
priority: CRITICAL
tags: [cve, cve-2026-123158, openssh, sshole]
Remediation Playbook: How to Neutralize SSHole Today
Remediating CVE-2026-123158 requires immediate, prioritized execution across three defensive layers:
Step 1: Emergency Workaround (Zero Downtime, 30 Seconds)
If you cannot deploy upgraded operating system packages immediately, disable password authentication across your SSH configuration. This immediately severs the exploit code path without disrupting existing SSH key-based access:
# 1. Update /etc/ssh/sshd_config (or drop into /etc/ssh/sshd_config.d/00-disable-passwords.conf)
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
# 2. Verify configuration syntax
sudo sshd -t
# 3. Restart SSH daemon (MANDATORY: changes do not apply until daemon reloads!)
sudo rc-service sshd restart
# Or on systemd-based musl setups:
# sudo systemctl restart sshd
CRITICAL OPERATIONAL WARNING: Simply modifying the configuration file or updating package files on disk does not protect the running server. OpenSSH retains listening sockets in memory under the original binary. You must restart the SSH daemon service for changes to take effect!
Step 2: Upgrade to OpenSSH 10.6p2
The OpenSSH security team patched the issue in OpenSSH 10.6p2. The patch alters auth-passwd.c to ensure that crypt() error sentinel returns (such as "*") are never treated as matching credentials against accounts whose stored hashes are also asterisks.
Alpine Linux backported the security fix across all active release branches (from Alpine 3.16 through 3.24). Update your system packages immediately:
# Update package repository index and upgrade openssh-server
apk update
apk upgrade openssh openssh-server
# Verify patched version
apk info -v openssh-server
# Restart sshd daemon
rc-service sshd restart
Step 3: Lock Shadow Entries with Double Bangs (!*)
If you must maintain service accounts or deployment users that should never accept password authentication, change their shadow fields from a single bare asterisk "*" to double-locked syntax "!*" or "!":
# Lock account with passwd -l (prefixes hash with '!')
sudo passwd -l alpine
# Result in /etc/shadow:
# alpine:!*:19842:0:99999:7:::
# Even if musl returns "*", strcmp("*", "!*") fails safely!
How Lorikeet Security's Autonomous AI Pentester (Lory) Validates SSHole
Modern cloud environments change constantly. Infrastructure-as-Code scripts spin up temporary debug bastion hosts, developer sandboxes, and Alpine microVMs that frequently bypass central hardening policies. Point-in-time annual penetration tests fail to detect these ephemeral exposures before attackers do.
At Lorikeet Security, our autonomous AI penetration testing engine—Lory—continuously models and tests attack surfaces across external perimeter assets and cloud workloads. Here is how Lory handles CVE-2026-123158:
- Non-Destructive Fingerprinting: Lory inspects SSH banners and protocol negotiation parameters, identifying OpenSSH daemons running on musl platforms without sending intrusive payloads.
- Safe Boundary Probing: Using dedicated, isolated test accounts, Lory validates whether
PasswordAuthenticationis active on key-only infrastructure without risking account lockouts or host crashes. - Human-in-the-Loop Countersigning: Every critical exploit finding generated by Lory is reviewed and countersigned by senior certified offensive security engineers before reaching your executive dashboard, ensuring zero false-positive alerts.
- Direct CI/CD & Fix Verification: Once your engineering team applies the configuration change or package update, Lory re-tests the endpoint on demand, verifying remediation within minutes.
Is Your Linux Infrastructure Exposed to SSHole?
Don't wait for threat actors to scan your public IP blocks for vulnerable OpenSSH daemons. Schedule an autonomous penetration test with Lorikeet Security and discover every authentication bypass, misconfiguration, and perimeter hole across your infrastructure today.
Frequently Asked Questions (FAQ)
Is SSHole remotely exploitable over the public internet?
Yes. If port 22 (or an alternate SSH port) is exposed to the internet, password authentication is enabled, and the host satisfies the five vulnerability criteria, any remote user can trigger the bypass without prior credentials or access keys.
Does CVE-2026-123158 affect OpenSSH on OpenBSD?
No. OpenBSD does not use the musl C library. OpenBSD uses its own native libc implementation and bcrypt-based password hashing routines, which do not exhibit the 256-byte truncation to asterisk behavior.
Why did it take ten years to discover this vulnerability?
Because the flaw only exists at the intersection of two separate codebases. OpenSSH developers assumed crypt() would never return an asterisk on valid salt formatting. musl developers assumed callers would treat "*" as an invalid hash that could never match user input. Because neither codebase had a bug in isolation, automated unit tests on either project alone never caught the condition.
Do Docker containers running Alpine Linux need patching?
Most Docker containers do not run an SSH daemon (they execute application code directly as PID 1). If your Alpine container does not run sshd, it is not vulnerable. However, if your container runs OpenSSH (such as an SFTP server, jump host, or development container), it must be patched or password authentication disabled.