【吃瓜速修】OpenSSH严重漏洞:长密码绕过认证"

admin 2026-11-10 17:08:02 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

【吃瓜速修】OpenSSH严重漏洞:长密码绕过认证

原创

泼猴
泼猴

表哥带我

2026年11月6日 11:46
广东

去阅读

OpenSSH 十年暗洞:一条 257 字节的密码,怎么把 \* 号账户全变成后门

昨天(11 月 5 日)OpenSSH 项目组扔了个 10.6p2 的补丁版出来,没有预告,没有发版投票,Damien Miller 在 release notes 里原话是\"别读了,如果你跑的是 musl libc 又没开 PAM,现在就去把 PasswordAuthentication 关了\"。

这个洞代号 \*sshole——注意开头那个星号,不是玩梗,是字面意义上的星号。漏洞的全部魔力,就发生在 /etc/shadow里那个 *字段和 musl libc 的 crypt()函数之间。

潜伏时间:接近十年。


事件时间线

时间 事件 关键细节
~2016 年 musl libc 的 crypt()实现落地\"超长密码返回 *作错误标记\"逻辑 设计本意是防止 DOS,因为 DES/crypt 类算法本来就只吃前 8 字节;musl 选择直接返回 *而不是截断
同期 OpenSSH 在无 PAM 构建下,遇到 shadow 字段为 *(无密码账户)时,会借另一个账户的 salt来做 hash 比对 这段\"借 salt\"逻辑本来是为了让 key-only 账户不至于在密码认证阶段直接崩掉
2026-11-05 研究者 Özgün Kültekin(Wakefield)把报告递到 OpenSSH 安全邮箱 OpenSSH 当天确认、当天出 10.6p2、Alpine 安全组同步发公告
2026-11-05 Alpine Linux 发布全套补丁包,从 3.16 到 edge 全分支覆盖 连已经 EOL 三天的 3.21 都专门回了移植补丁
2026-11-06 NVD 收录 CVE-2026-123158,CVSS 3.1 初评 8.1(High) 评分还在动,NVD 分析挂起中

这个洞到底是怎么炸的

正常流程应该是这样

sshd 没开 PAM 的时候,密码认证是它自己处理的:

  1. 用户输入密码 hello123
  2. sshd 调 crypt(输入密码, 存储的hash),把输入密码用存储 hash 里的 salt 重新 hash 一遍
  3. 比对新算出来的字符串和 shadow 里存的字符串,一样就放行

问题出在两个\"各管各\"的决策撞车

撞车点 1:某个账户(比如你 adduser建的、只发 SSH key 的运维账户)在 /etc/shadow里长这样:

deploy:*:19800:0:99999:7:::

那个 *不是密码,是\"这个账户没有密码,谁都别想用密码登进来\"的意思。这是 Unix 几十年的老惯例。

撞车点 2:sshd 看到 *太短,里面没有合法 salt,它不会直接拒绝,而是从同机器另一个带 $5$或 $6$hash 的账户那里借一个 salt 过来,再拿这个 salt 去 hash 攻击者提交的密码。

撞车点 3(最骚的):musl 的 crypt()有个规定——当传入的密码超过 256 字节时,它不 hash 了,直接返回字符串 *表示\"输入非法,老子不干了\"。

于是攻击链就闭合了:

攻击者发一个 300 字节的密码
        ↓
sshd 看到目标账户 shadow 字段是 *
        ↓
从隔壁某真实账户借个 $6$salt$xxx 过来
        ↓
调 musl 的 crypt(300字节垃圾, 借来的salt)
        ↓
musl 一看:你这密码超 256 字节了,返回 \"*\"
        ↓
sshd 把 crypt() 返回的 \"*\" 和 shadow 里存的 \"*\" 做字符串比对
        ↓
两个 \"*\" 一模一样 → 认证通过 → 拿到 shell

不需要知道任何密码,不需要爆破,一次连接,必中。

Alpine 安全组的原文写得很直白:\"It takes one connection, works every time, and needs no retries.\"


谁在目标上

OpenSSH 开发者 Damien Miller 自己讲得很清楚:

❝

\"This vulnerability does not exist on non-Linux systems, Linux/glibc systems and Linux systems that use PAM for password authentication.\"

你的系统 中招没
Alpine Linux(默认 musl + 无 PAM 的 openssh-server) 中
OpenWrt / Buildroot / Yocto 里编了 OpenSSH + musl + 无 PAM 大概率中 ,看具体固件配置
Debian / Ubuntu / RHEL / CentOS / Fedora(glibc + PAM) 不中
macOS / \*BSD / Windows 不中
任何已经 PasswordAuthentication no的机器 不中(密码路径直接关了)
用 openssh-server-pam且 UsePAM yes的 Alpine 不中
密码字段是 !的锁定账户 不中(那种是彻底锁死,不是 *)
容器里没跑 sshd 的 Docker 用户 不中

但光看发行版不够,你还得同时满足下面四个条件才真的可被打:

  1. sshd 确实在用 musl 的 crypt()自己验密码(没走 PAM)
  2. PasswordAuthentication yes(这是 OpenSSH 默认值,很多人从来没改过)
  3. 机器上至少存在一个 shadow 字段为 *的账户——Alpine 基础镜像从 3.16 开始 root默认就是 root:*
  4. 机器上至少存在一个带 $5$(SHA-256)或 $6$(SHA-512)hash 的真实账户——这个几乎是必然,因为管理员总得给自己设个登录密码

最阴的一个场景:Alpine 官方的 tiny-cloud 镜像(AWS AMI / Azure VHD / qcow2)里带一个叫 alpine的默认用户,密码字段是 *,而且配置了免密 doas 到 root。这意味着攻击者一旦通过这个洞登进 alpine,doas 一下就是 root。Alpine 官方自己都承认了:首启安全,但只要你后来给任何账户设了密码(cloud-init 加用户、控制台改密码),这台机器立刻变成敞开门的状态。


本地自检

纯环境探测

#!/bin/bash
# sshhole-check.sh — CVE-2026-123158 自检
# 用法: sudo bash sshhole-check.sh

set -u
echo \"===== sshhole (CVE-2026-123158) self-check =====\"

# 1. 是不是 musl 系统
MUSL=0
if ldd --version 2>&1 | grep -qi musl; then
    echo \"[1/4] libc: musl detected\"
    MUSL=1
else
    echo \"[1/4] libc: $(ldd --version 2>&1 | head -1) — not musl, likely not affected\"
fi

# 2. OpenSSH 版本
echo \"[2/4] sshd version: $(ssh -V 2>&1)\"

# 3. sshd 实际生效配置
echo \"[3/4] sshd effective config:\"
sshd -T 2>/dev/null | grep -Ei 'passwordauthentication|usepam|permitrootlogin' | sed 's/^/      /'

# 4. shadow 里有几个 * 账户、有没有 $5$/$6$ hash
STAR_ACCTS=$(awk -F: '$2 == \"*\" {print $1}' /etc/shadow)
HASH_ACCTS=$(awk -F: '$2 ~ /^\\$[56]\\$/ {print $1}' /etc/shadow)

echo \"[4/4] shadow field analysis:\"
echo \"      accounts with '*' (key-only/no-pass): $(echo \"$STAR_ACCTS\" | grep -c . )\"
[ -n \"$STAR_ACCTS\" ] && echo \"        -> $(echo $STAR_ACCTS | tr '\
' ' ')\"
echo \"      accounts with \\$5\\$/\\$6\\$ hash:        $(echo \"$HASH_ACCTS\" | grep -c . )\"

# 综合判定
AFFECTED=0
if [ \"$MUSL\" = \"1\" ]; then
    PAM=$(sshd -T 2>/dev/null | awk '/^usepam/{print $2}')
    PW=$(sshd -T 2>/dev/null | awk '/^passwordauthentication/{print $2}')
    NSTAR=$(echo \"$STAR_ACCTS\" | grep -c .)
    NHASH=$(echo \"$HASH_ACCTS\" | grep -c .)
    if [ \"$PAM\" = \"no\" ] && [ \"$PW\" = \"yes\" ] && [ \"$NSTAR\" -ge 1 ] && [ \"$NHASH\" -ge 1 ]; then
        AFFECTED=1
    fi
fi

echo
if [ \"$AFFECTED\" = \"1\" ]; then
    echo \">>> RESULT: VULNERABLE — patch now or set PasswordAuthentication no\"
else
    echo \">>> RESULT: not exploitable under current config (still patch when you can)\"
fi

把这段存成 sshhole-check.sh,chmod +x之后 sudo 跑。输出 VULNERABLE的,别犹豫,往下看修复那节。

测试

如果你手头上有一台自己的、可以随便重装的Alpine 虚拟机,想亲眼验证这个绕过,流程是这样:

# ===== 在目标测试机上(Alpine 虚拟机,root 执行)=====
# 1. 建一个 key-only 账户(密码字段自动是 *)
adduser -D -s /bin/sh deploy
usermod -p '*' deploy
# 注意:这台机器上你自己 root 登录时设过密码,所以 $6$ hash 已经存在

# 2. 确认 sshd 配置
grep -E '^PasswordAuthentication|^UsePAM' /etc/ssh/sshd_config
# 应该看到 PasswordAuthentication yes,且没有 UsePAM yes

# ===== 从另一台机器(攻击机)发起验证 =====
# 3. 生成一个 300 字节的超长密码字符串
LONGPASS=$(python3 -c \"print('A'*300)\")

# 4. 用超长密码尝试以 deploy 登录(deploy 根本没设密码)
sshpass -p \"$LONGPASS\" ssh \\
    -o StrictHostKeyChecking=no \\
    -o PreferredAuthentications=password \\
    -o PubkeyAuthentication=no \\
    deploy@<目标IP> 'id; cat /etc/os-release | head -2'

第 4 步如果打印出 uid=...(deploy)和 Alpine 的版本信息——你就复现了。正常情况下,deploy这个账户你根本不知道密码(也确实没密码),ssh 一定会打 Permission denied。但在受影响的机器上,一次就进。


修复

路线 A:能治本

Alpine 已经给全分支出了包,直接:

apk update
apk upgrade openssh
rc-service sshd restart    # 关键!不重启 sshd,跑的还是老进程

各分支修复版本号对一下:

Alpine 分支 修复后 openssh 版本
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

\\升级完必须 rc-service sshd restart\\。Alpine 安全组专门强调了这件事:包升级不会替换正在跑的守护进程,你不重启等于没修。

路线 B:治标不治本,但马上生效

如果你暂时升不了级——比如固件在路由器里、Yocto 镜像要重新烧录——先把密码认证路径掐了:

# 保险起见,先确认你的 SSH key 能登进去,另开一个会话留着别关
mkdir -p /etc/ssh/sshd_config.d
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-passwords.conf

# 验证配置语法,再重启
sshd -t && rc-service sshd restart

# 看一眼结果,应该输出 passwordauthentication no
sshd -T | grep -i passwordauthentication

做这步之前,千万确认你自己的公钥已经在目标机器的 authorized\_keys 里,并且另开一个终端测试过新 key 登录。手一抖把密码和 key 都关了,云主机只能去控制台救援。

路线 C:检查有没有被打过

Alpine 给了排查清单,我搬过来:

#日志里有没有\"用密码登了一个本来没密码的账户\"这种鬼话
grep 'Accepted password for' /var/log/messages | grep -E ' (alpine|root|deploy|git|) '

# 查 doas / sudo 的免密记录
cat /etc/doas.conf 2>/dev/null
cat /etc/sudoers 2>/dev/null
ls /etc/sudoers.d/ 2>/dev/null

# 看最近有没有被新建账户、被塞 authorized_keys
awk -F: '$3>=1000{print $1\":\"$3}' /etc/passwd
find /home -name authorized_keys -newer /etc/hostname -exec ls -la {} \\;

任何一条对得上、你又没法确定是自己干的—— 别犹豫,整机重装,别想着\"我先清一下后门\"。这种级别的未授权访问,留着就是定时炸弹。


几句题外话

这个洞最值得琢磨的地方,不是它有多难,而是它有多平淡。

musl 的作者做了一个完全合理的决定:密码太长就拒绝,返回 *。OpenSSH 的作者做了一个完全合理的决定:shadow 里的 *是个占位符,别因为它崩了,借个 salt 继续走流程。两边代码都没问题,十年里无数人 review 过,但两个\"合理\"撞在一起,就变成了\"发 300 个 A 就能登录\"。

这不是第一回,也不会是最后一回。2024 年的 regreSSHion 是信号处理竞态,同一年的 XZ 后门是压缩库被供应链污染,今天这个是 libc 错误返回值和 shadow 占位符的语义撞车。sshd 的核心协议逻辑早就被磨得很光滑了,真正出事的地方全在它和周边世界的接缝上。

对运维的实际启发就一条:如果你跑的是 musl 系发行版(Alpine、OpenWrt、各种嵌入式),PasswordAuthentication no不应该是\"最佳实践\",应该是默认值。key-only 不是更安全的选项之一,是唯一选项——这次被打穿的,恰恰就是那些\"我已经用 key 登录了,密码字段反正就是个 *,无所谓\"的机器。


对此你怎么看在评论区发表你的意见吧

其他推荐阅读

【吃瓜】腾讯游戏部分官方启动器被投毒

【吃瓜速查】ARTEX二开项目存在后门

AI正在悄悄改写网安挖洞的游戏规则

Claude官方扩展被投毒植入持久化SSH后门

【吃瓜】无印良品泄露2726万条信息

【吃瓜】朝鲜黑客盗走交易所60亿美元

【吃瓜】如何看待手机中的数据采集拉低性能

    评论:0   参与:  0