输入300个A,就能SSH登进\"没密码\"的账号?"

admin 2026-11-09 01:48:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

输入 300 个 A,就能 SSH 登进\"没密码\"的账号?

原创

pippybear
pippybear

安全无界

2026年11月6日 09:06
上海

去阅读

在 musl libc(Alpine、OpenWrt、Buildroot、Yocto 以及大量 Docker 镜像)上,不走 PAM 的 OpenSSH,会让 shadow 里密码字段为 * 的账号接受任何 257~1024 字符的\"密码\"。root、纯密钥的部署账号、LDAP 账号、Alpine 云镜像的默认用户,都在影响范围内。

近日,安全研究员 Ozgun Kultekin(Wakefield)公开了这个被称为\*sshole的问题。披露页面称,这个行为自2016 年起就存在于 musl 平台的 OpenSSH 中,已经潜伏近十年。

说明:本文信息主要来自披露页面(sshhole.com)及其引用的公开提交记录。截至撰文,页面未标注本问题的 CVE 编号,修复版本以页面所述的 OpenSSH 10.6p2 为准,请以 OpenSSH、Alpine 官方公告为准。


一、漏洞原理

这个漏洞不是某一行代码写错了,而是几个各自合理的设计,在十年间叠在一起,最后产生了一个认证绕过。

1. 背景:\* 代表\"没有任何密码能匹配\"

早期 Unix 把密码哈希直接放在 /etc/passwd。1980 年 Bill Joy 给 daemon 账号的密码字段写了一个 ,表示这个账号不允许登录。 不是合法哈希,没有任何密码能算出它,这个约定沿用至今。

现在哈希放在 /etc/shadow,形如 $6$salt$hash($6$ 表示 sha512)。登录时 sshd 用同样的 salt 对用户输入做哈希,比对结果是否一致。字段是 \* 时,正常情况下永远比不上。

2. 第一块拼图:musl 的 crypt() 失败时返回 \"\*\"

按 POSIX,crypt() 失败应返回 NULL,但很多程序不检查 NULL,直接拿返回值比较,结果崩溃。

2012 年 8 月,musl 改成:crypt() 失败时不再返回 NULL,而是返回一个\"不可能匹配\"的字符串,字符选的是 \*。提交说明的意思是,\"总是成功但返回不可匹配哈希\"比\"出错失败\"更好。

3. 第二块拼图:sha512crypt 的 256 字节上限

20 天后,musl 加入 sha512crypt。由于 sha512crypt 的耗时随密码长度增长,攻击者可以用超长密码拖住服务器。所以 musl 加了一个 KEY\_MAX 256 的上限:密码超过 256 字节,直接判定失败,即返回\"\*\"。

4. 第三块拼图:sshd 借别人的 salt 来\"拖时间\"

2016 年,OpenSSH 修复 CVE-2016-6210(通过响应时间差枚举用户名):对不存在的用户,sshd 以前直接拒绝,比真实用户快几毫秒,攻击者可据此判断用户是否存在。

修法是:没有真实哈希可比对时,sshd借用另一个账号的 salt,照样对你输入的密码算一遍哈希,只为消耗同样的时间,最终仍然拒绝。

页面指出,这个\"没有真实哈希\"的分支,除了不存在的用户,也包括 shadow 字段为\*的真实用户。

5. 三块拼图叠加

对于 shadow 字段为 \* 的账号,攻击者提交一个 257~1024 字符的密码:

  1. sshd 借用另一个账号的 $6$ salt,调用 crypt() 处理这个密码;
  2. 密码超过 256 字节,musl 的 sha512crypt 失败,crypt() 返回 \"*\";
  3. sshd 把返回值 \"*\" 与 shadow 里的 \"*\" 比较,相等;
  4. 认证通过,登录成功。

6. 为什么十年没被发现

披露页面给出的原因:

  • 全新系统没设置任何密码时不受影响,需要系统里还有一个带真实哈希的账号;
  • 触发需要一个超长的\"错误密码\",日常不会碰到;
  • OpenSSH 的上游测试跑在 OpenBSD 和 glibc 上,musl 下没有 sshd 的测试;
  • 大多数发行版走 PAM,而 PAM 对 * 字段根本不会调用 crypt()。

页面还提到,Debian 在 2013 年就有人建议 glibc 借鉴 musl 的 \"\*\" 做法;2024 年 12 月,musl 邮件列表上也有人讨论 busybox passwd 在 257 字符密码下的异常,但都没有联系到 sshd。研究者表示,他是在对 sshd 做大规模差分测试(glibc、musl,PAM、非 PAM,shadow、非 shadow,约 17,600 次异常登录)时,发现\"musl 且无 PAM\"这个组合与其他所有组合行为不一致,从而定位到问题。


二、影响范围

触发条件(需同时满足)

条件 说明
运行在 musl 上 Alpine、OpenWrt、Buildroot、Yocto,以及大量基于 Alpine 的 Docker 镜像
sshd 不走 PAM Alpine 的 openssh-server 默认不使用 PAM,或配置了 UsePAM no
开启密码登录 PasswordAuthentication 默认开启
存在 * 账号 shadow 密码字段为 *:root、纯密钥用户、LDAP 用户、Alpine 云镜像默认用户
存在其他真实密码账号 供 sshd 借用 salt

满足以上条件时,该 \* 账号接受任何 257~1024 字符的密码。

危害程度

  • 在 Alpine 官方的 AWS、Azure 镜像上,默认用户 alpine 带有免密 doas,绕过认证等于直接拿到 root;
  • 如果 root 账号本身是 * 且允许 root 登录,则直接获得 root;
  • 任何 * 账号被登录后,攻击者都能以该账号身份落地,作为横向移动的起点。

不受影响的环境

  • glibc 系统(Debian、Ubuntu、Fedora 等);
  • 走 PAM 认证的系统;
  • Dropbear;
  • OpenBSD。

容易被忽略的暴露面

  • 容器镜像:很多业务镜像基于 Alpine,并顺手装了 openssh-server 方便调试,且从未清理;
  • 嵌入式与网络设备固件:OpenWrt、Buildroot、Yocto 构建的设备;
  • 云上 Alpine AMI / 镜像:默认用户 + 免密提权的组合最危险;
  • 暴露在公网或内网跳板上的 22 端口。

三、如何排查

第 1 步:判断主机是否在影响面内

# 是否 muslldd --version 2>&1 | grep -i musl # OpenSSH 版本(页面称 10.6p2 修复)ssh -V # sshd 的生效配置:是否走 PAM、是否允许密码登录、root 登录策略sshd -T | grep -Ei 'usepam|passwordauthentication|permitrootlogin' # 列出密码字段为 * 的账号awk -F: '$2==”*”{print $1}' /etc/shadow # 是否存在带真实哈希的账号awk -F: '$2 ~ /^\\$/{print $1}' /etc/shadow

判定:musl、usepam no、passwordauthentication yes、存在 \* 账号,同时满足即为受影响。

第 2 步:查日志中\"不该出现的密码登录\"

核心思路:\ 账号在正常情况下不可能通过密码登录成功。只要日志里出现 Accepted password for <某个 \ 账号>,就非常可疑。

# Alpine 默认使用 busybox syslog;有 journald 的系统改用 journalctl -u sshdLOG=/var/log/messages for u in $(awk -F: '$2==”*”{print $1}' /etc/shadow); do  grep ”Accepted password for $u ” ”$LOG”done

关注点:

  • 来源 IP 是否陌生、是否来自公网;
  • 登录时间是否异常;
  • 配合 last、lastb、wtmp 查看登录会话;
  • 日志轮转和保留周期是否足够:该行为自 2016 年存在,但实际能追溯的窗口通常很短,日志里没有记录不能证明没被利用。

注意:sshd 日志不会记录密码内容,因此无法直接从日志里识别\"超长密码\",只能靠\"\* 账号出现密码登录成功\"这个异常来判断。

第 3 步:对自有主机做黑盒验证(仅限已授权资产)

sshpass -p ”$(python3 -c 'print(”A”*300)')” \\  ssh -o PubkeyAuthentication=no <锁定账号>@<主机> id

能登录成功,就说明该主机存在此问题。请只在你拥有或获得明确授权的系统上测试。

第 4 步:批量盘点(镜像和基础设施层)

  • 扫描所有基于 Alpine 的镜像:apk info openssh-server、Dockerfile 中的 apk add openssh;
  • 用 Trivy、Grype 等工具对镜像里的 openssh 包版本做一次扫描;
  • 检查云上 Alpine AMI、跳板机、堡垒机模板;
  • 业务容器通常不需要 sshd,盘点出来后优先评估是否直接移除。

第 5 步:如果发现已被利用

按\"已失陷\"处理,不建议原地修复:

  • 检查各用户 ~/.ssh/authorized_keys 是否被追加公钥;
  • 检查新增用户、/etc/passwd 与 /etc/shadow 变更;
  • 检查 cron、启动脚本、/etc/doas.conf、/etc/sudoers;
  • 检查异常进程、异常外连、可疑文件;
  • 轮换该主机上的所有凭据、密钥和 token;
  • 重建主机或镜像,并保留取证数据。

四、修复与缓解

首选:升级

apk upgrade openssh

披露页面称OpenSSH 10.6p2已修复该问题。请同时关注 Alpine 等发行版的安全公告,确认各自修复版本。

暂时无法升级:关闭密码登录

echo ”PasswordAuthentication no” > /etc/ssh/sshd_config.d/00-no-passwords.confrc-service sshd restart

其他加固建议

  • 如果业务允许,改用 PAM 认证路径;
  • 用安全组、防火墙、堡垒机限制 22 端口的来源;
  • 对云镜像默认用户做收敛:移除不必要的免密提权;
  • 对不需要 sshd 的容器,直接去掉 openssh-server;
  • 把\"musl + 非 PAM + 允许密码登录\"加入基线检查,纳入镜像准入和主机巡检。

五、小结

这个问题的有趣之处在于,它没有任何一个\"明显的错误\":

  • 1980 年,* 表示不可登录;
  • 2012 年,musl 让 crypt() 失败时返回 \"*\",避免程序崩溃;
  • 2012 年,musl 为防 DoS 给密码设了 256 字节上限;
  • 2016 年,OpenSSH 为防用户枚举,借别人的 salt 做假哈希;
  • 2022 年,Alpine 为兼容 key 登录,把 root 的锁定标记从 ! 改成 *。

每一步单看都合理,叠在一起,就变成了\"超长密码可以登录没有密码的账号\"。对安全团队来说,它也提醒我们:libc 和认证之间那一层\"没人看\"的胶水代码,值得被测试和审计。


参考资料

  1. \*sshole 披露页面:https://sshhole.com/
  2. musl 提交:crypt 失败时返回不可匹配哈希(2012):https://git.musl-libc.org/cgit/musl/commit/?id=b3c4cc121f70faea45389fe7ddc1127ed5cbd8bb
  3. musl 提交:加入 sha256/sha512 crypt(2012):https://git.musl-libc.org/cgit/musl/commit/?id=88bf5a8a8d7d796f63cca8589f4de67aa8345f1a
  4. musl crypt_sha512.c(密码长度上限):https://git.musl-libc.org/cgit/musl/tree/src/misc/crypt\_sha512.c?id=88bf5a8a8d7d796f63cca8589f4de67aa8345f1a#n189
  5. OpenSSH 提交:为无效用户确定合适的 salt(2016,对应 CVE-2016-6210):http://lists.mindrot.org/pipermail/openssh-commits/2016-July/005428.html
  6. OpenSSH 提交:修复 bcrypt 哈希的伪造 salt 生成(2026-10-30):http://lists.mindrot.org/pipermail/openssh-commits/2026-October/013271.html
  7. Alpine aports:root 锁定标记由 ! 改为 *(3.16):https://gitlab.alpinelinux.org/alpine/aports/-/commit/fbe926afc76de76e07d078f266badc4144dde8e3
  8. Debian Bug #714219(建议 glibc 采用 * 作为失败返回):https://bugs.debian.org/714219
  9. musl 邮件列表:busybox passwd 与 257 字符密码讨论(2024-12):https://www.openwall.com/lists/musl/2024/12/31/2
  10. Single UNIX Specification:crypt() 返回值定义:https://pubs.opengroup.org/onlinepubs/7908799/xsh/crypt.html

    评论:0   参与:  0