Sshole
3002 points • 7 days ago • Article Link

在基于 musl 的 Linux 发行版(如 Alpine 、 OpenWrt 和 Yocto)中,OpenSSH 长期存在一个安全漏洞。该漏洞源于 libc 处理身份验证错误的方式,与 OpenSSH 为缓解计时攻击所采取的措施之间的相互作用。十多年来,配置为使用单个星号作为密码占位符的账户(这是希望仅依赖 SSH keys 的用户常见做法)无意中接受了任何超过 256 个字符的密码。该缺陷使攻击者能够绕过安全防护,获取那些本应明确禁止基于密码登录的账户的未授权访问。

该问题可追溯到 2012 年。当时 musl 库修改其 crypt 函数以避免系统崩溃。从历史来看,如果身份验证函数无法计算哈希值,它会返回一个 NULL 指针,许多程序因为未检查这一情况而会崩溃。 musl 通过改为返回一个字面星号字符来修复这一点:它假设由于不存在任何有效哈希能与字面星号匹配,结果会被当作密码验证失败。然而,这一修复与同时引入的 256 字节密码长度限制发生了重叠,用于防止拒绝服务攻击。当密码长度超过限制时,musl 的 crypt 函数会失败并返回同样的星号。

更复杂的是,OpenSSH 在 2016 年引入了一套用于防止计时攻击的缓解策略。当用户尝试登录一个不存在的账户时,OpenSSH 通常会立即拒绝登录。为防止攻击者根据响应时间推断有效用户名,OpenSSH 开始从系统中存在的账户里随机挑选一个有效 salt,以执行"伪哈希"计算。这样可以确保服务器在拒绝有效用户和无效用户时耗时一致。结果,OpenSSH 反而不经意间提供了触发 musl 漏洞所需的关键条件。

当用户试图针对一个密码域为星号的账户进行身份验证时,OpenSSH 会判断需要执行用于隐藏计时的哈希计算。它会从系统中另一个账户取得有效 salt,并对用户输入的内容进行哈希处理。如果输入长度超过 256 字节,musl 的 crypt 函数就会返回星号。由于被访问账户的密码字段同样是星号,比较结果会被判定为匹配。于是,任何长度超过 256 个字符的密码都能充当锁定账户的"通用密钥",包括许多默认的 Alpine Linux 云映像中的 root 用户。

由于大多数标准 Linux 发行版依赖 glibc 和 PAM,它们在处理这类身份验证流程时方式不同,且不会受到相同的交互影响,因此该漏洞基本被忽视了十年。该问题主要影响那些在没有 PAM 的情况下使用 musl 的系统——这种配置在轻量级容器和云环境中很常见。建议用户立即升级到 OpenSSH 10.6p2,或在配置文件中将 PasswordAuthentication 设置为 no 以降低风险。同时建议检查日志中是否出现异常的长字符串密码输入尝试,以判断系统是否已经被入侵。

752 comments • Comments Link

• 该漏洞源于在使用 `musl` libc 时,`sshd` 中存在身份验证绕过问题,触发原因在于该库对 `crypt()` 的实现并不符合常规。该实现会在错误条件下返回标记字符串("" 或 "+"),而不是返回 `NULL`,从而被 OpenSSH 误判为匹配成功的密码哈希。

• 利用该漏洞需要满足一组特定且较为苛刻的条件:必须使用 `musl` libc,禁用 PAM,启用基于密码的身份验证;目标账户的密码哈希必须为 ``;同时还需要在同一个连接中进行多次尝试。

• 批评者认为,`musl` 开发人员在失败时返回任意字符串而不是 `NULL`,违反了 `crypt()` 的 POSIX 规范,本质上是为了避免程序崩溃而牺牲了应有的错误信号传递机制。

• 对带内信号(In-band signaling)的依赖——例如用 "*" 或 "!" 这类无实际意义的字符串作为元数据——被视为 Unix-like 环境中一种系统性的设计缺陷。此类缺陷往往会引发危险的歧义,并导致安全疏忽。

• C 语言缺少现代类型系统和显式结果处理机制,使得这些长期存在的错误类别得以延续。许多旧式 C API 习惯用哨兵值(Sentinel values)表示错误,而这些值很容易被调用方忽略或被处理逻辑错位。

• 此次发现也体现出 Agent swarms 与 AI 辅助的安全研究在定位复杂漏洞链方面的效率不断提升。此类漏洞链往往会被人工审查遗漏,尽管这些报告有时会存在文风不一致或明显带有"AI 生成"的口吻。

• 面向系统加固的建议包括:彻底禁用基于密码的身份验证,使用带硬件支持的密钥(Hardware-backed keys),并避免将 SSH 服务暴露在公共互联网上——不论特定的 libc 或操作系统配置是否被认为足够安全。

• 关于 `musl` 在错误处理上选择"掩盖"的做法,业界存在争议:这究竟是为兼容遗留系统而做出的务实但带风险的妥协,还是一种更根本的错误决策——即优先避免崩溃而非优先保证安全。

• 一些人质疑该漏洞在真实场景中的严重程度,认为它在现代标准容器环境中难以复现。但对于配置错误的遗留系统或专用系统而言,它依然是一个严重风险。

• 整体讨论反映出对软件漏洞"永恒"状态的更广泛挫败感,以及在维护陈旧 API 契约与推动向更安全、更结构化的编程范式演进之间持续存在的紧张关系。

本次讨论聚焦于与 `musl` libc 和 OpenSSH 相关的复杂身份验证绕过问题,并将其作为一个关于带内信号处理以及静默错误处理风险的案例研究。各方形成了强烈共识:根本原因在于 C 语言遗留的错误报告模式——必须通过 `NULL` 或错误字符串等哨兵值来表达失败,而这些值极易在危险情况下被误解。尽管部分参与者强调让遗留系统继续运行的务实必要性,仍有不少人指出,该漏洞再次说明了使用现代编程语言以及通过强制显式错误处理的结构化数据格式的优势。最终,这次对话凸显了安全研究正从"被动观察"向"AI 增强的发现"方式转变的趋势,并引发人们对未来软件维护以及在关键系统基础中持续累积的技术债务的质疑。