# OpenSSH password authentication bypass on musl libc without PAM (CVE-2026-123158)

Defensive threat-hunting and exposure-check pack. Built 2026-11-05 (Europe/London, GMT). No exploit
payloads are included and none should be added. The checkers never connect to sshd and never send a password.

The KQL in `kql/` and the converted Sigma in `sigma/converted/` have NOT been run on a live Sentinel or
Defender tenant. Only syntax and offline conversion were checked. Fields marked `[UNVERIFIED]` need checking
against your data first.

## Verified facts (primary sources, checked 2026-11-05)

| Field | Value | Source |
|---|---|---|
| Identifier | CVE-2026-123158, published 2026-11-05 15:46 GMT by MITRE (CNA). Alpine's advisory (same day, earlier) said no CVE had been assigned at the time of writing | cve.org CVE record (cveawg API), Alpine advisory |
| NVD | Status "Received" (2026-11-05 16:17 GMT), no NVD score yet | NVD API |
| Severity | CNA score CVSS 3.1 8.1 High, `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H`, CWE-394 | cve.org CVE record |
| OpenSSH wording | Release notes: "This release contains a critical security workaround that affects Linux systems that use the musl libc and PAM disabled." OpenSSH says it considers the root cause to be outside sshd but exploitable via sshd and avoidable there. This is descriptive text, not a formal severity rating | OpenSSH 10.6p2 release notes |
| Affected range | CVE record: OpenSSH versions below 10.6p2 on Linux (from version 0). Release notes give no lower bound | cve.org CVE record, release notes |
| Fix | Portable OpenSSH 10.6p2, released 2026-11-05. Commit `4353fdc43ef646c43a8dda7025cc1a898caf1482` ("avoid password authn bypass", 2026-11-04 21:18 GMT) touches `auth-passwd.c`, `openbsd-compat/port-uw.c`, `openbsd-compat/xcrypt.c`; it rejects stored password fields shorter than 4 characters | Release notes, openssh-portable commit |
| Root cause | Without PAM, sshd compares `crypt()` output with the stored field and borrows another account's salt when the stored field is too short. musl `crypt()` returns `*` on error for keys over 256 bytes, which equals the stored `*` field | Alpine advisory |
| Password length | Alpine: "257 to 1024 bytes". CVE text: "any password between 256 and 1024 characters" (1024 is sshd's `MAX_PASSWORD_LEN`) | Alpine advisory, CVE record, fix commit |
| Four conditions | 1) `openssh-server` (built without PAM), or `openssh-server-pam` with `UsePAM no`; 2) `PasswordAuthentication yes` (OpenSSH default); 3) an account whose password field is `*`; 4) at least one local account with a `$5$` or `$6$` hash | Alpine advisory |
| `*` accounts | `root` (ships `root:*` since about 3.16), key-only users from `adduser` without a password or `usermod -p '*'`, musl-nscd directory users (no shadow entry), tiny-cloud `alpine` user | Alpine advisory |
| root | Default `PermitRootLogin prohibit-password` protects root; root is directly reachable only with `PermitRootLogin yes`, but another reachable account that can become root (for example via doas) still gives root | Alpine advisory |
| tiny-cloud | Official tiny-cloud images (qcow2, VHD, AMI) 3.18.11 through 3.24.3 ship `alpine` with `*` and passwordless doas. Safe on first boot; exploitable once any `$6$` hash exists (console password, user-data `hashed_passwd`) | Alpine advisory |
| Not affected | No sshd in container; `PasswordAuthentication no`; `openssh-server-pam` with `UsePAM yes`; locked `!` accounts; no `$`-style hash on the host; cloud-init images (write `PasswordAuthentication no`) | Alpine advisory |
| Alpine releases | Every release checked: 3.21 to 3.24 and edge, plus 3.16 to 3.20 (no longer regularly updated; 3.21 end of support 2026-11-01) | Alpine advisory |
| Alpine fixed packages | edge 10.6_p2-r0; 3.24 10.3_p1-r2; 3.23 10.2_p1-r1; 3.22 10.0_p1-r11; 3.21 9.9_p2-r1; 3.20 9.7_p1-r6; 3.19 9.6_p1-r3; 3.18 9.3_p2-r4; 3.17 9.1_p1-r7; 3.16 9.0_p1-r6 | Alpine advisory; aports APKBUILD on master (10.6_p2-r0), 3.24-stable (10.3_p1-r2), 3.21-stable (9.9_p2-r1) |
| Point releases | 3.21.10, 3.22.8, 3.23.8, 3.24.4 contain the fix (released 2026-11-05) | Alpine release announcement |
| Remediation | `apk upgrade -U -a`, then `rc-service sshd restart`. Upgrading does not restart a running sshd | Alpine advisory |
| Workaround | `PasswordAuthentication no` and restart sshd. Drop-in `/etc/ssh/sshd_config.d/` is read only from Alpine 3.19; older releases need the main file edited (advisory gives a `sed` that also covers `Match` blocks). Check with `sshd -T \| grep -i passwordauthentication` | Alpine advisory |
| Detection | `sshd` lines in `/var/log/messages` reading `Accepted password for` an account with no password; review doas use, new users, files and authorized keys | Alpine advisory |
| KEV | Not listed. CISA KEV catalog version 2026.11.04 (released 2026-11-04 20:00 GMT, 1768 entries); no OpenSSH entry matches. The catalog predates the CVE | CISA KEV JSON feed |
| Exploitation | No primary source reports exploitation in the wild. The CVE record references public lab and reproduction repositories (not reviewed, not included here) | CVE record references |
| Credit | Reported by security researchers | OpenSSH release notes, Alpine advisory |

Primary sources:
- https://www.openssh.org/releasenotes.html#10.6p2
- https://github.com/openssh/openssh-portable/commit/4353fdc43ef646c43a8dda7025cc1a898caf1482
- https://alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/
- https://alpinelinux.org/posts/alpine-3.21.10-3.22.8-3.23.8-3.24.4-released/
- https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/main/openssh/APKBUILD
- https://www.cve.org/CVERecord?id=CVE-2026-123158
- https://nvd.nist.gov/vuln/detail/CVE-2026-123158
- https://www.cisa.gov/known-exploited-vulnerabilities-catalog (feed: known_exploited_vulnerabilities.json)
- https://attack.mitre.org/techniques/T1078/003/ , T1098/004, T1548/003 (MITRE ATT&CK tags used in Sigma)

## Corrections to the initial report

| Teammate claim | Finding |
|---|---|
| No CVE yet | CVE-2026-123158 was published by MITRE at 15:46 GMT on 5 Nov. True when Alpine posted, out of date now |
| OpenSSH rates the fix Critical | Not a formal rating. The release notes say the release "contains a critical security workaround" (lowercase, descriptive). The only score is the CNA's CVSS 8.1 High |
| Default root logs in | root has `*` but is protected by the default `PermitRootLogin prohibit-password`; only `PermitRootLogin yes` makes root directly reachable. Escalation through another account (alpine plus doas) still reaches root |
| PAM off | More precisely: the non-PAM `openssh-server` package, or `openssh-server-pam` with `UsePAM no` |
| 257 to 1,024 bytes | Matches Alpine. The CVE text says "between 256 and 1024 characters" |
| tiny-cloud images reach root | Correct, with the caveat that they are safe on first boot until any `$6$` hash exists |
| glibc distros not affected | Not stated as such. OpenSSH scopes it to "Linux systems that use the musl libc and PAM disabled"; Alpine explains the musl `crypt()` behaviour. glibc exclusion is an inference |
| OpenWrt, Buildroot, Yocto may be affected | Not mentioned in any primary source. Plausible for musl builds of OpenSSH without PAM, unverified |
| Not exploited, not in KEV (2026.11.04) | Confirmed: catalog 2026.11.04, not listed, no primary report of exploitation. Public lab or repro repositories are referenced by the CVE record |
| Fix, commands, fixed branches, point releases | Confirmed, with the per-branch package versions above. Fix is in OpenSSH 10.6p2; Alpine stable branches carry a backport patch, not 10.6p2 |
| (missing) | Alpine `APKBUILD` `secfixes` comments do not yet list CVE-2026-123158 (checked master, 3.24-stable, 3.21-stable); the 3.24-stable commit is "main/openssh: fix password authentication" (2026-11-05 08:59 GMT) |

## Contents

| Path | Purpose |
|---|---|
| `kql/openssh-musl-password-bypass-hunting.kql` | Q1 Syslog Accepted password for root/alpine/key-only; Q2 first-try password success; Q3 DeviceLogonEvents; Q4 CommonSecurityLog; Q5 DeviceFileEvents authorized_keys, doas, sudoers, passwd/shadow; Q6 doas execution; Q7 DeviceTvmSoftwareInventory + DeviceInfo openssh status vs fixed table; Q8 Alpine/embedded scope list |
| `sigma/*.yml` | 3 rules: sshd accepted password for root/alpine, authorized_keys/doas/sudoers writes, doas execution |
| `sigma/converted/` | Kusto output for no pipeline, microsoft_xdr, microsoft_365_defender, sentinel_asim, azure_monitor |
| `scripts/check-openssh-musl-bypass.sh` | Bash checker |
| `scripts/Check-OpenSSHMuslBypass.ps1` | PowerShell 7 checker, same logic |
| `tests/` | Fixtures for every verdict, bash tests, Pester tests, `run_all.sh` |

Long password attempts: sshd does not log the password or its length, so there is no log-based query for the
257 to 1024 byte attempt. Q2 (password success with no prior failure from that source) is the closest proxy.

### Sigma results

`sigma check`: 0 errors, 0 condition errors, 0 issues. Conversion to Kusto:
- `lnx_auditd_authorized_keys_or_doas_change.yml` and `lnx_doas_execution.yml`: convert on all pipelines. Note
  `azure_monitor` maps them to `SecurityEvent` (a Windows table), which is not useful for Linux; prefer the XDR,
  ASIM or hand-written KQL.
- `lnx_sshd_accepted_password_keyonly_account.yml` (logsource `linux/sshd`, keywords): fails on microsoft_xdr,
  microsoft_365_defender, sentinel_asim and azure_monitor ("Unable to determine table name"); converts only without
  a pipeline (bare keyword expression). Use KQL Q1 instead.

## Checkers

Strictly read-only and offline. They read a mounted host root (or a copy), an optional saved `sshd -T` dump,
an optional saved auth log, or an inventory CSV. Never run them on files containing real hashes outside the
host itself; fixtures use placeholders only. Hash values are never printed, only account names and counts.

```
check-openssh-musl-bypass.sh --root /mnt/host [--sshd-t sshd_T.txt] [--authlog messages]
check-openssh-musl-bypass.sh --csv inventory.csv
check-openssh-musl-bypass.sh --authlog messages [--star-users deploy,backup]
pwsh Check-OpenSSHMuslBypass.ps1 -Root /mnt/host [-SshdT f] [-AuthLog f] | -Csv f | -AuthLog f [-StarUsers a,b]
```

What they read: `lib/apk/db/installed` (openssh package, `openssh-server-pam`, `musl-nscd`), `var/lib/opkg/status`,
version and `libpam.so` strings in sshd binaries, `etc/ssh/sshd_config` with `Include` (first global value wins;
any `Match` block setting `PasswordAuthentication yes` counts as yes), `UsePAM`, `PermitRootLogin`, `etc/shadow`
and `etc/passwd` (`*` fields, `$5$`/`$6$` counts), `ld-musl-*` vs `ld-linux`/`libc.so.6`, doas config for the
tiny-cloud `alpine nopass` rule.

Verdicts and exit codes: 0 PATCHED, 1 LIKELY VULNERABLE (all four conditions listed), 2 UNKNOWN, 3 NOT EXPOSED
(states which condition is false), 4 SUSPICIOUS LOGIN (auth log has `Accepted password for` root, alpine or a `*`
account; overrides), 5 usage/read error. CSV exits with the worst row.

Version logic: Alpine package is patched if `>= 10.6_p2-r0`, or the same pkgver as a fixed branch package with
pkgrel at or above it. Non-Alpine version strings are compared to upstream 10.6p2 only (vendor backports are not
recognised, so a backported build may show as unfixed). PATCHED means the package is fixed on disk; restart sshd.

CSV header: `host,libc,apk_openssh,openssh_version,pam,password_authentication,star_accounts,hash_accounts`.

## Tests

`bash tests/run_all.sh`: shellcheck, PSScriptAnalyzer, 35 bash tests, 28 Pester tests, a guard against any
257+ byte token in fixtures or scripts, a guard against network primitives in scripts, 12 bwrap runs with
`--unshare-net` and a read-only root, a sha256 before/after check of the pack, `sigma check` and conversions.

## Remediation (Alpine)

1. `apk upgrade -U -a`
2. `rc-service sshd restart` (required; upgrading does not restart the running daemon)
3. If you cannot update: `PasswordAuthentication no`, `sshd -t && rc-service sshd restart`, confirm with `sshd -T`.
4. If an `Accepted password for` a `*` account cannot be ruled out, treat the host as compromised.
