# CVE-2026-123158 (*sshole): OpenSSH Password Authentication Bypass on Alpine Linux

musl crypt() returns "*" on overlong passwords, colliding with shadow's no-password marker, letting sshd accept logins for key-only accounts.

- Payout: Research
- Severity: high
- Program: OpenSSH
- Write-up by: StepSecurity
- Disclosed: 2026-11-06
- CVE: CVE-2026-123158
- Write-up quality: 8/10
- Original: https://www.stepsecurity.io/blog/cve-2026-123158-sshhole-openssh-musl-password-bypass
- Library page: https://bugbountylibrary.com/r/blog%3Astepsecurity-io-blog-cve-2026-123158-sshhole-openssh-musl-password-bypass
- Tags: area:oss domain:infra domain:oss impact:ato impact:privesc kind:research platform:threat-intel sev:high tech:linux technique:source-review vuln:auth-bypass vuln:misconfiguration

**Target:** sshd password authentication on musl libc (Alpine)
**Root cause:** musl crypt() returns "*" on failure (input >256 bytes) instead of NULL, colliding with the shadow convention of "*" meaning no password; OpenSSH's non-PAM path borrows a salt for hashless accounts and compares the failed crypt result to the stored "*", matching.
**Steps:**
1. Confirm target: musl libc, no PAM, PasswordAuthentication yes, an account with shadow field `*`, and at least one real `$5$`/`$6$` hash on host.
2. Open SSH connection and send a throwaway/wrong password as first attempt (first attempt always rejected).
3. On second prompt send a password of 257-1024 bytes (e.g. `257 x A`).
4. sshd's crypt fails on length, returns `*`, comparison against stored `*` succeeds, login accepted as the passwordless account.
**How found:** Original finder noticed musl crypt() returns the string "*" on failure rather than NULL, and that OpenSSH's CVE-2016-6210 salt-borrowing path for hashless accounts compares crypt output to the stored shadow field, making the two "*" values collide.
**Exploitation:** Unauthenticated network attacker reaching sshd logs in as a key-only account (shadow field `*`) by sending a >256-byte password on a second auth attempt in the same connection; root additionally needs PermitRootLogin yes.
**Escalation:** Logging in as a passwordless account with doas/sudo rights (e.g. Alpine tiny-cloud `alpine` user) yields privilege escalation; root path requires PermitRootLogin yes.
**Insight:** When a library signals failure with a sentinel string that also has meaning in the consumer's data format, look for comparison paths that treat the sentinel as valid data; here crypt()'s "*" equals shadow's "*" no-password marker. Also: bypass needs a failed first attempt, so single-shot scanners miss it.

## Proof of concept (copied exactly from the original report)

**Lab test matrix showing 257-byte password accepted**

```
[T1 alpine + wrong, then 256 x A ] REJECTED
[T2 alpine + wrong, then 257 x A ] ACCEPTED -> uid=1000(alpine)
[T3 alpine + wrong, then 300 x A ] ACCEPTED -> uid=1000(alpine)
[T4 alpine + 300 x A, 1st attempt] REJECTED
[T7 authsource + 300 x A ] REJECTED (real password hash - immune)
[T11 alpine + wrong, then 1023 x A] ACCEPTED
```

**Auth log showing failed then accepted password**

```
sshd-session[1926]: Failed password for alpine from 10.0.2.2 port 59008 ssh2
sshd-session[1926]: Accepted password for alpine from 10.0.2.2 port 59008 ssh2
```

**PAM flavor test blocking the bypass**

```
[F1 alpine + wrong, then 300 x A ] REJECTED
[F2 alpine + wrong, then 257 x A ] REJECTED
[F3 authsource + correct password ] ACCEPTED
```

