SSHole: Inside the Critical OpenSSH Authentication Bypass on musl libc (CVE-2026-123158) | Lorikeet Security Skip to main content
Back to Blog

SSHole: Inside the Critical OpenSSH Authentication Bypass on musl libc (CVE-2026-123158)

Lorikeet Security Threat Research Team November 7, 2026 16 min read CVE-2026-123158 · CVSS 8.1
SSHole: CVE-2026-123158 OpenSSH Authentication Bypass on musl libc

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:

/etc/shadow entry for a key-only deployment user shadow
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:

+----------------------------------------------------------------------------------------------------+ | SSHOLE EXPLOIT EXECUTION FLOW | +----------------------------------------------------------------------------------------------------+ [ Attacker ] | | 1. SSH_MSG_USERAUTH_REQUEST (user: "alpine", password: 300 x "A") v [ OpenSSH sshd (auth-passwd.c) ] | |-- 2. Reads /etc/shadow: target user "alpine" has hash = "*" |-- 3. To prevent CVE-2016-6210 timing enumeration, sshd decides to compute crypt() anyway! |-- 4. sshd borrows valid salt from root account (e.g., "$6$saltsalt$...") |-- 5. Calls musl crypt(password_300_bytes, borrowed_salt) | +-------------\ \ (Password length = 300 bytes > 256 byte musl limit) v [ musl libc (src/crypt/crypt.c) ] | |-- 6. strlen(key) > 256 -> Triggers key limit exhaustion! |-- 7. Returns error sentinel string: "*" | +-------------/ v [ OpenSSH sshd (auth-passwd.c) ] | |-- 8. Evaluates authentication: strcmp(encrypted, shadow_hash) | strcmp("*", "*") == 0 -----> TRUE! | v [ AUTHENTICATION SUCCESSFUL ] | +--> Allocates PTY -> Spawns Interactive Shell as "alpine" +--> Default Alpine cloud image allows passwordless "doas -s" -> INSTANT ROOT! +----------------------------------------------------------------------------------------------------+

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:

auth-passwd.c (OpenSSH simplified authentication verification logic) C
/* 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:

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:

Default Alpine Linux Cloud Image (AWS / Azure / OpenStack) Configuration bash
# 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:

Auditing a Linux Host for CVE-2026-123158 (Safe Local Inspection) bash
# 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:

Surveillance Query: Unexpected Password Logins for Star-Locked Accounts syslog / splunk
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:

/etc/audit/rules.d/30-sshole-detection.rules auditd
# 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:

falco_rules.local.yaml (Container Threat Detection) yaml
- 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:

Disabling Password Authentication in sshd_config bash
# 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:

Applying Alpine Linux Security Updates bash
# 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 "!":

Hardening Account Shadow Fields bash
# 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:

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.

Lory waving

Hi, I'm Lory! Need help finding the right service? Click to chat!