Alpine Linux published a security advisory on November 5, 2026 for an authentication bypass in its openssh-server package. An attacker who can reach the SSH port sends a password between 257 and 1,024 bytes and is logged in to any account whose password field is *, the value Alpine uses for accounts with no password. Alpine says it takes one connection and works every time. OpenSSH released 10.6p2 the same day with a workaround in sshd, and MITRE assigned CVE-2026-123158 (CVSS 3.1 8.1). OpenSSH considers the root cause to be outside sshd, in how musl’s crypt() reports errors, but fixed it in sshd because that is where it can be exploited. Alpine shipped fixed packages for every branch back to 3.16, including 3.21, which reached end of support on November 1. Upgrading the package is not enough on its own. The running sshd keeps the old code until it is restarted. How the bypass works Alpine’s default openssh-server is built without PAM, so sshd checks a password itself: it hashes the attempt with crypt() and compares the result to the account’s entry in /etc/shadow. When the stored entry is too short to contain a salt, as * is, sshd borrows the salt from another account’s hash. musl’s crypt() returns the string * to signal an error when the password is longer than 256 bytes. * is also exactly what is stored for accounts with no password. The comparison matches and sshd accepts the login. Three conditions must all hold: an account has * as its password field at least one other local account has a $5$ (SHA-256) or $6$ (SHA-512) password hash, which supplies the salt password authentication is enabled in sshd, which is the OpenSSH default One account with a real password is enough to expose every account without one. Who is exposed, in priority order Alpine hosts with SSH reachable from the internet, password authentication on, and any account with *. Treat these as possibly compromised if they were reachable after November 5, when the details became public. Instances built from official Alpine cloud images (tiny-cloud) 3.18.11 to 3.24.3. They include an alpine user with * as its password and passwordless doas, so a login becomes root. They are safe on first boot and become exploitable as soon as any $6$ hash exists on the machine, for example a console password or hashed_passwd in user-data. Hosts with PermitRootLogin yes. Alpine has shipped root with * since about 3.16, so root itself is reachable when root login is allowed. Key-only users created without a password or with usermod -p '*', and directory users (nslcd, winbind, sssd through musl-nscd) that have no shadow entry. Alpine-based container images that run sshd with password authentication on, such as SFTP and jump-host images. Containers that do not run sshd are not affected. Other Linux systems that build OpenSSH against musl without PAM. The CVE covers any of them. Ask your vendor whether its build uses PAM. Not affected: openssh-server-pam with UsePAM yes, systems with password authentication off, accounts locked with ! rather than *, systems with no $5$ or $6$ hashes, and cloud-init images that set PasswordAuthentication no. Systems using glibc, such as Debian, Ubuntu and RHEL, are not affected. Affected and fixed versions Alpine branch Fixed openssh package edge 10.6_p2-r0 3.24 10.3_p1-r2 (point release 3.24.4) 3.23 10.2_p1-r1 (point release 3.23.8) 3.22 10.0_p1-r11 (point release 3.22.8) 3.21 (end of support November 1, 2026) 9.9_p2-r1 (point release 3.21.10) 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 Upstream, the fix is in OpenSSH 10.6p2. Cloud images built from 3.21.10, 3.22.8, 3.23.8, 3.24.4 and later include it. The linuxserver.io openssh-server image moved to the fixed 3.24 package in release 10.3_p1-r2-ls242 on November 5. How to check a host Run these on each Alpine host or container that runs sshd. They only read state: cat /etc/alpine-release apk info -v openssh-server openssh-server-pam 2>/dev/null sshd -T | grep -E '^(passwordauthentication|usepam|permitrootlogin) ' awk -F: '$2=="*"{print "no password: " $1}' /etc/shadow grep -cE '^[^:]*:\$[56]\$' /etc/shadow The host is exposed if the package is below the fixed version for its branch, passwordauthentication is yes, usepam is no or the PAM package is not installed, at least one account has no password, and the last command prints 1 or more. If the package is already fixed, make sure sshd has been restarted since it was installed. For container images, run the apk info and sshd -T commands inside each running container, or check the image’s base Alpine version in your registry scanner. Mitigation and remediation Upgrade and restart sshd: apk upgrade -U -a rc-service sshd restart If you cannot upgrade today, turn off password authentication. Confirm that key-based login works first and keep your current session open. On Alpine 3.19 and later: echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-password.conf sshd -t && rc-service sshd restart sshd -T | grep passwordauthentication On 3.18 and older, which ignore sshd_config.d, set PasswordAuthentication no in /etc/ssh/sshd_config itself, including inside any Match blocks. Rebuild Alpine-based images that run sshd, and redeploy the containers. Pulling a new base image does not change containers already running. Update cloud instances launched from tiny-cloud images 3.18.11 to 3.24.3, or turn off password authentication on each one. Rebuild your golden images from 3.21.10, 3.22.8, 3.23.8 or 3.24.4 or later. Lock accounts that should never log in with a password using ! rather than * (passwd -l <user>), and set PermitRootLogin prohibit-password or no. Take SSH off the internet where it does not need to be there, or restrict it to known source addresses. Threat hunting Alpine’s sshd logs a successful bypass as an ordinary Accepted password for <user> line in /var/log/messages. From OpenSSH 9.8, which Alpine 3.21 and later ship, the line comes from the sshd-session process rather than sshd, so the queries match both. Password length is not logged, so the signal is a password login to an account that has no password, or on a host where logins should only use keys. A successful bypass also produces no failed attempts first. Microsoft Sentinel queries The KQL below is example code for Microsoft Sentinel with Alpine syslog forwarded to the Syslog table. Adapt table and field names to your environment and test each query before relying on it. Replace the example host and account lists with your own, or use watchlists. Hunt 1: password logins to accounts that have no password // Example: Sentinel Syslog, sshd password logins to accounts that should be key-only let AlpineHosts = dynamic(["alpine-web-01", "alpine-bastion-01"]); // your Alpine hosts, or a watchlist let NoPasswordAccounts = dynamic(["root", "alpine"]); // add key-only and service users Syslog | where TimeGenerated > ago(30d) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has "Accepted password for" | parse SyslogMessage with * "Accepted password for " User " from " SrcIP " port " SrcPort " " * | where Computer in~ (AlpineHosts) or User in~ (NoPasswordAccounts) | extend ExternalSource = not(ipv4_is_private(SrcIP)) | project TimeGenerated, Computer, User, SrcIP, ExternalSource, SyslogMessage | order by TimeGenerated desc Any hit for root, alpine or a key-only user on a host that was unpatched at the time should be treated as a compromise. Hunt 2: first password login on a host that normally uses keys // Example: Sentinel Syslog, password logins on hosts and accounts that used only public keys before let Baseline = Syslog | where TimeGenerated between (ago(90d) .. ago(7d)) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has "Accepted " | parse SyslogMessage with * "Accepted " Method " for " User " from " * | summarize Methods=make_set(Method) by Computer, User; Syslog | where TimeGenerated > ago(7d) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has "Accepted password for" | parse SyslogMessage with * "Accepted password for " User " from " SrcIP " port " * | join kind=leftouter Baseline on Computer, User | where isnull(Methods) or not(set_has_element(Methods, "password")) | project TimeGenerated, Computer, User, SrcIP, PreviousMethods=Methods | order by TimeGenerated desc Hunt 3: doas or sudo soon after a password login On tiny-cloud images the alpine user can run doas without a password, so a bypass becomes root in one step. // Example: Sentinel Syslog, privilege use within 30 minutes of an sshd password login let PasswordLogins = Syslog | where TimeGenerated > ago(30d) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has "Accepted password for" | parse SyslogMessage with * "Accepted password for " User " from " SrcIP " port " * | project LoginTime=TimeGenerated, Computer, User, SrcIP; Syslog | where TimeGenerated > ago(30d) | where ProcessName in~ ("doas", "sudo") or SyslogMessage has_any ("doas:", "sudo:") | project PrivTime=TimeGenerated, Computer, PrivMessage=SyslogMessage | join kind=inner PasswordLogins on Computer | where PrivTime between (LoginTime .. LoginTime + 30m) | project LoginTime, PrivTime, Computer, User, SrcIP, PrivMessage | order by LoginTime desc Hunt 4: one source logging in by password to several hosts A scanner using the bypass gets in on the first try, so look for one address with successful password logins across many hosts and no failures. // Example: Sentinel Syslog, sources with password logins to several hosts and no failed attempts let Window = 1d; let Accepted = Syslog | where TimeGenerated > ago(30d) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has "Accepted password for" | parse SyslogMessage with * "Accepted password for " User " from " SrcIP " port " * | summarize Hosts=dcount(Computer), HostList=make_set(Computer, 20), Users=make_set(User, 10), FirstSeen=min(TimeGenerated) by SrcIP, bin(TimeGenerated, Window); let Failed = Syslog | where TimeGenerated > ago(30d) | where ProcessName in~ ("sshd", "sshd-session") and SyslogMessage has_any ("Failed password", "Invalid user") | parse SyslogMessage with * " from " SrcIP " port " * | summarize Failures=count() by SrcIP; Accepted | where Hosts >= 3 | join kind=leftouter Failed on SrcIP | where isnull(Failures) or Failures == 0 | order by Hosts desc Hunt 5: SSH to Alpine hosts allowed from the internet This shows which Alpine hosts are reachable on SSH from outside and should be patched and checked first. // Example: Sentinel CommonSecurityLog, inbound SSH allowed from public addresses to Alpine hosts let AlpineIPs = dynamic(["203.0.113.10", "10.20.0.15"]); // your Alpine host addresses CommonSecurityLog | where TimeGenerated > ago(30d) | where DestinationPort == 22 and DestinationIP in (AlpineIPs) | where DeviceAction in~ ("allow", "accept", "permitted", "pass") | where not(ipv4_is_private(SourceIP)) | summarize Connections=count(), Sources=dcount(SourceIP), SampleSources=make_set(SourceIP, 20), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by DestinationIP, DeviceVendor | order by Sources desc Responding to a possible compromise Keep a copy of /var/log/messages and any forwarded logs before changing anything, and note the source addresses of the password logins. Upgrade openssh and restart sshd, or turn off password authentication, then take the host off untrusted networks. Look for persistence: new entries in /etc/passwd and /etc/shadow, changes to every ~/.ssh/authorized_keys, /etc/doas.conf and /etc/doas.d/, OpenRC services and scripts in /etc/init.d/ and /etc/local.d/, cron entries in /etc/crontabs/, and new files in /tmp, /var/tmp and /dev/shm. Rotate secrets stored on the host or reachable from it: SSH keys, API tokens, cloud instance credentials and database passwords. Rebuild the host or container if a bypass login is confirmed or cannot be ruled out. Containers should be redeployed from a fixed image, not cleaned in place. Indicators of compromise None have been published. The bypass uses a normal SSH login, so the hunts above look for password logins where none should happen rather than for addresses or files. Timeline November 1, 2026: Alpine 3.21 reaches end of support. November 5, 2026: Alpine publishes its advisory and fixed openssh packages for 3.16 to edge. OpenSSH releases 10.6p2. MITRE publishes CVE-2026-123158. linuxserver.io ships a fixed openssh-server image. November 6, 2026: Proof-of-concept code is public. CVE-2026-123158 is not in CISA KEV, and FIRST has not yet scored it in EPSS. Frequently Asked Questions Is this a bug in OpenSSH or in Alpine? Both are involved. musl’s crypt() returns * as an error value, and that is also the stored value for accounts with no password. OpenSSH 10.6p2 adds a workaround in sshd, and Alpine shipped it in its packages. Does the attacker need a valid account name? Yes, but the common ones are predictable: root where root login is allowed, and alpine on official cloud images. We use SSH keys only. Are we safe? Only if password authentication is turned off in sshd. Key-only accounts usually have * as their password, which is exactly what the bypass targets. Check with sshd -T | grep passwordauthentication. We upgraded the package. Is that enough? No. Restart sshd with rc-service sshd restart. The old daemon keeps running until you do. Are Alpine containers affected? Only those that run sshd with password authentication on. Most Alpine containers do not run sshd at all. Is Debian, Ubuntu or RHEL affected? No. They use glibc, and their OpenSSH builds use PAM. Is it being exploited? No exploitation in the wild has been reported, but proof-of-concept code is public and the attack takes one connection. Assume scanning has started. How ThreatDefence is responding ThreatDefence has added CVE-2026-123158 to our vulnerability intelligence. Where we hold Linux syslog, firewall and cloud telemetry, our SOC is: identifying Alpine hosts and containers with SSH reachable from untrusted networks alerting on password logins to root, alpine and key-only accounts, and on privilege use that follows them helping customers confirm the fixed package and the sshd restart across their fleet If you run Alpine servers or images and want help checking them or investigating a suspicious login, contact us. Read more about our managed detection and response. Sources Alpine Linux, Security advisory: openssh-server password authentication bypass (November 5, 2026) OpenSSH, release notes for OpenSSH 10.6p2 (November 5, 2026) OpenSSH, portable OpenSSH fix commit 4353fdc CVE Program, CVE-2026-123158 record (November 5, 2026, updated November 6, 2026) linuxserver.io, docker-openssh-server release 10.3_p1-r2-ls242 (November 5, 2026) CISA, Known Exploited Vulnerabilities catalog (checked November 6, 2026)