Alpine Linux sshhole漏洞复现

发布于: 2026-11-07 13:30

漏洞描述

SSHole 漏洞(漏洞编号 CVE-2026-123158,CVSS 评分 8.1 高危)是安全研究员 Özgün Kültekin 于 2026 年 11 月披露的一个史诗级 OpenSSH 身份验证绕过漏洞。

当攻击者针对特定配置的系统,在密码框中输入超过 256 个字符(通常为 257 至 1024 字节)的任意超长密码时,即可无需任何正确凭证,单次连接直接登录系统中本应“禁止密码登录”的特定账号。

漏洞原理

musl libc 的错误处理设计(2012年)

在 Linux 中,传统的 /etc/shadow 文件中如果一个账号的密码字段被设为 *,代表该账号“未设置密码/禁止密码登录”(因为没有任何密码能哈希出单字符 *)。但在 2012 年,为了防止某些未检查 NULL 返回值的程序崩溃,轻量级标准 C 库 musl libc 做出调整:当密码哈希计算失败时,不返回 NULL,而是返回单字符 *。同时,musl 为了防止超长密码引发拒绝服务(DoS),将密码输入上限限制在 256 字节,超过该长度直接报错,而报错的返回值也是 *。

OpenSSH 的防时序攻击设计(2016年)

为了修复由于计算耗时不同导致黑客可以枚举用户名的漏洞(CVE-2016-6210),OpenSSH 引入了一个逻辑:即使遇到没有密码哈希的账号(例如密码字段为 * 的账号),sshd 也会故意借用系统中另一个活跃账号的盐值(Salt)对输入的密码算一遍哈希,以对齐响应时间。这导致原本应该被直接拒绝的 * 账号,也走入了密码哈希的计算流程。

逻辑闭环触发绕过

当攻击者向一个密码字段为 * 的账号(如 Alpine 的 root 或默认用户)发起 SSH 密码登录,并输入一个 257 字节的任意字符串时,OpenSSH 借用其他账号的盐值调用 musl 的 crypt() 函数。musl 发现密码超长,直接报错并返回字符串 *。OpenSSH 拿到返回值 * 与 /etc/shadow 中存储的字段 * 进行比对——完全一致,验证通过,直接放行。

漏洞前置条件

运行在 musl libc 环境:目标系统必须使用 musl 库(如 Alpine Linux、OpenWrt、Buildroot、Yocto 等),使用标准的 glibc 系统(如 Ubuntu, Debian, Fedora, CentOS)完全不受影响。

未启用 PAM(可插拔认证模块):OpenSSH 编译或运行时没有使用 PAM(Alpine 的 openssh-server 默认不启用 PAM)。

开启了密码登录:SSH 配置文件中开启了 PasswordAuthentication yes(通常为默认值)。

存在密码字段为 * 的活跃账号:目标系统中存在通过密钥或证书登录(Key-only)、或通过 LDAP 介接、或默认无密码的活跃账号,其 shadow 文件中的密码字段严格为 *(注:锁定的账号如果是 ! 则不受影响)。

存在另一个有真实密码的本地账号:系统中必须至少存在一个使用了 SHA256/SHA512 真实密码哈希的账号,用于让 sshd 借用盐值。

漏洞复现

在 2026-11-05 Alpine Linux 的官方安全通报 中,详细说明修复了 openssh-server 从 3.16 版本到最新版本。

下面使用 docker 复现漏洞,如果你没有docker环境或拉取速度慢,可以使用这个项目安装换源来加速https://github.com/SuperManito/LinuxMirrors。

# GNU/Linux 更换系统软件源
bash <(curl -sSL https://linuxmirrors.cn/main.sh)
# Docker 安装与换源
bash <(curl -sSL https://linuxmirrors.cn/docker.sh)

根据官方安全通报可知,Alpine Linux 3.15 版本及以下版本的 openssh-server 并未进行修复,仍然存在漏洞,可以用来快速复现。通过 Dockerfile 构建一个 Alpine Linux 3.15 镜像,创建两个账号:一个密码字段为 * 的账号 demo(禁止密码登录),另一个账号 alice 设置了真实密码 correcthorse。然后通过 SSH 客户端尝试使用超长密码登录 demo 账号,即可触发漏洞。

FROM alpine:3.15

RUN apk add --no-cache openssh-server \
    && ssh-keygen -A \
    && mkdir -p /run/sshd

RUN adduser -D alice && echo 'alice:correcthorse' | chpasswd
RUN adduser -D demo && sed -i 's/^demo:[^:]*:/demo:*:/' /etc/shadow
RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D", "-e"]

编译运行

docker build -t alpine-sshhole .
docker run -d --name alpine-sshhole -p 2222:22 alpine-sshhole

生成 payload(大于256个字符即可触发漏洞)

python3 -c "print('A'*300)"

注意,漏洞机制决定了第一次密码尝试会失败,第二次才会成功。

由于 musl libc 的 getspnam() 复用同一静态缓冲区,第一次密码尝试时,sshd 为借用盐值读取真实账户数据,会覆盖目标账户的 shadow 条目,导致比较失败。第二次尝试时,pick_salt() 的缓存已就绪,不再触发缓冲区覆盖,目标账户的 * 字段得以保留,与 musl 对超长密码返回的 * 匹配成功。因此,必须在同一次 SSH 连接内连续输入两次密码,第一次任意短密码,第二次超长密码。

停止容器删除镜像

docker stop alpine-sshhole
docker rm alpine-sshhole
docker rmi alpine-sshhole

漏洞修复

  • 升级到修复了该漏洞的 OpenSSH 版本。
  • 临时禁用密码登录:在 /etc/ssh/sshd_config 中设置 PasswordAuthentication no。