SSH安全现状离谱:一个基础协议如何坑遍全球服务器
本文是视频《SSH安全现状离谱:一个基础协议如何坑遍全球服务器》(作者:Low Level)的精读笔记,配套中配视频观看效果更佳;另附完整文稿|English transcript。
这期视频讲一个离谱到近乎黑色幽默的安全漏洞:在 Alpine Linux 这类系统上,攻击者只要在 SSH 登录框里输入一段超过 256 个字符的“密码”,就能直接登进 root。Low Level 用一条清晰的主线,把 1980 年的一颗星号、2012 年的一个返回值改动和 2016 年的一个好意补丁串成完整因果链——哪怕你不懂密码学,也能看懂“全球服务器是怎么被两个互不知情的函数联手放倒的”。
图:视频封面——SSH 认证绕过漏洞主题开场画面
全景:一条主线看懂
这是一次完整的漏洞考古:一个“256 个字符即可登录”的认证绕过,源头要追溯到 1980 年 Bill Joy 在 shadow 文件里定下的星号约定,以及 2012 年 musl libc 让 crypt 函数失败时也返回星号的改动。中间的引线,是 2016 年为堵计时攻击而让“无密码账号也真实执行加密”的好意补丁——三条线索在 2026 年对齐,拼成一条潜伏十年的登录绕过链。全片最后落在定性反思上:这不是内存安全漏洞,Rust 也救不了。
图:漏洞因果链示意——1980 星号 → 2012 返回值改动 → 2016 补丁 → 256 字符绕过
一、事件定性:一串超长密码,敲开了服务器的门
论点:这不是又一起普通 CVE,而是一出“复古味十足”的认证绕过。
- 意外抢戏:Low Level 原本计划讲 Linux TCP/IP 栈里那个 CVSS 9.8 分(接近满分)的 CVE,结果在 Twitter 上刷到这个 SSH 本身的漏洞,当场改档期,还吐槽“以为十月网络安全月过了就能消停”。
- 范围澄清:并非所有 SSH 都中招,漏洞只存在于少数发行版,但这些发行版(如 Alpine)在容器世界里到处在用。这是一次“组合缺陷(combined flaws)”,由 Wakefield 团队研究员 Ozgun Coltecun(厄兹贡·科尔泰昆)发现,视频基于团队公开仓库与那份极清楚的 TLDR 展开。OpenSSH 与 musl libc 已发布紧急修复,涉及数百万系统,需立即打补丁。
- 历史对照(例证):他 2013 年第一次参加黑帽大会时上了一门质量欠佳的入侵课,教的是在 2008/2009 年的 Solaris 机器上做缓冲区溢出——在登录提示符输入 294 个字母 A 就能拿到 shell。而 2026 年,“输入超长字符串即可登入系统”的问题依然存在。
- 关键定性:这个 bug 虽然要往缓冲区塞大量字符,但它不是缓冲区溢出;本质是安全边界与函数之间的“约定”随时间彼此漂移,人们逐渐忘记了两个互相依赖的函数各自该干什么。
“登录提示符输入 294 个 A 就能拿到 shell,那是 2008、2009 年的机器。都 2026 年了,这问题还在。” —— Low Level,用 2013 年黑帽大会的旧课对照当下漏洞时感叹
图:2013 年黑帽大会课程场景示意——294 个 A 换一个 shell
他还顺带评了当下的大环境:感觉每天都有新的疯狂零日漏洞从天上掉下来,网络被攻击只是“何时”,而不是“是否”。
本节要点:漏洞核心现象是“256+ 字符的密码即可登录”,出现在少数但铺天盖地使用的发行版上;它是认证绕过而非缓冲区溢出,根子是函数间约定的漂移。
二、两颗星号:1980 年的锁,与 2012 年的“好意”
论点:漏洞的两半各自都“无害”,拼起来才致命。第一半,是 shadow 文件里的星号。
- 登录流程:用密码登录 SSH 时(与公钥认证无关),系统把你输入的密码经一系列函数处理,与 /etc/shadow 中的记录比对,底层调用的是 libc 的 crypt 函数。
- 星号=锁:shadow 里有些值根本不是哈希——root 条目可能是一个星号(*),表示该账号没有密码。这是 1980 年 Bill Joy 为 daemon 账号定下的约定。安全性在于:星号不是合法哈希,哈希碰撞永远不会发生,所以谁也登不进这类账号。
- 哈希格式:shadow 的哈希字段格式是“算法、盐、哈希”,
$6前缀代表 SHA-512;盐是哈希前拼接进数据的基础密码学概念。
第二半,是 musl libc 的返回值改动。
- 背景:libc 有多种实现,最常见的是 GNU libc;musl 是轻量、快速、简单、免费、力求正确的另一实现。
- 2012 年 8 月的决定:过去 crypt 失败(加密出问题、数据太多或太少)时返回 null,但很多程序写得不好、不检查返回值,null 反而带来麻烦;于是 musl 改为失败时返回一个字符——正是星号。
- 当时的判断:星号恰好与 1980 年定下的“无密码”标记撞脸,但大家认为无所谓,因为理论上不该存在任何会把“crypt 失败返回值”与“无密码标记”放在一起比较的场景。
“没什么大不了,因为反正不应该有任何场景会去比较这两个值——哈。” —— Low Level,讲完 2012 年 musl 改动后的反讽笑声,为后续剧情埋下伏笔
图:/etc/shadow 文件示例——root 条目的密码字段显示为星号
本节要点:1980 年的星号是“不可登录”的锁,2012 年的星号是“出错时的占位符”;两者相等纯属巧合,而“永远不会有场景比较它们”这个假设,是后面一切故事的种子。
三、埋雷的补丁:2016 年反计时攻击的“借哈希”
论点:真正把两颗星号接上电的,恰恰是一次为了安全的修复。
- 被修的问题:2016 年,有人发现 musl libc 的加密实现存在计时攻击——通过响应时间的微小差异推断秘密信息。
- 攻击原理(例证):想探测一台机器上有哪些用户,就尝试登录不存在的 Bob——后台不执行任何加密运算,密码输入立刻失败秒退;而尝试真实存在的 Alice 时,即使密码错误也要真跑一遍加密,耗时约一秒。快慢之差,足以枚举出系统里的全部账号。
- 2016 年 7 月的修复方案:对没有密码或根本不存在的账号,系统临时“借用”(steal/borrow)另一个真实账号的哈希和盐,让加密后端在攻击者输入上做一次假校验,制造等效的加密耗时,消除侧信道。比如 Bob 不存在,就借 Alice 的盐跑一遍。
- 关键引申:root 是空密码(星号)时,系统也会借用另一个用户的 SHA-512 算法与哈希,对输入内容真跑一遍运算——“无密码账号不跑加密”这个旧前提,被这次修复正式推翻了。
图:计时攻击与修复示意——Bob 秒退、Alice 慢一秒,补丁借用真实盐值制造等效耗时
本节要点:2016 年补丁动机正当(堵计时侧信道),做法却是让“不存在/无密码的账号”也真实执行加密——恰好为星号账号接上了“可能失败、可能返回星号”的路径。
四、利用链闭合:256 个字符如何变成“合法密码”
论点:三条历史线索在此对齐,认证被一串 'a' 击穿。
- 触发点:读 musl libc 中 SHA-512 crypt 的实现可以发现,若输入长度超过 key max(常量 256),函数直接返回 0。
- 级联:SHA-512 crypt 返回 0 → crypt 出错 → 返回星号。
- 对撞:如果 root 在 shadow 里的哈希恰好是星号,strcmp 比较“算出的哈希”与“存储的哈希”——两个星号相等,密码校验直接通过。
“算出来的是星号,shadow 里存的也是星号,两个一样,密码校验就过了。” —— Low Level,揭示完整利用链、讲到 strcmp 对撞成功时直呼疯了
图:利用链收束画面——两个星号在 strcmp 中相等
- 责任归属:这是 musl libc 实现层面的失误;最常见的 glibc 完全不受影响。
- 攻击成本:条件满足时,输入约 257~1024 个 'a'(作者认为 1024 是长度上限)即可直接拿到 shell。
五个前提条件(全部满足才能利用):
| # | 前提条件 | 关键细节 |
|---|---|---|
| 1 | 开启密码认证 | 该选项默认开启;公钥认证与本案无关 |
| 2 | sshd 未启用 PAM | 部分发行版默认随包启用 PAM,这类配置免疫 |
| 3 | 存在密码字段为星号的账号 | 如 root,存于 /etc/shadow;无 shadow 文件、哈希直接放 /etc/passwd 的系统同样中招 |
| 4 | 存在 SHA-512 真实密码账号 | $6 前缀(当前默认,$1 为 MD5),供补丁“借用”哈希与盐 |
| 5 | 输入超长密码 | 257~1024 个字符,越过 key max=256 即触发返回 0 |
图:musl SHA-512 crypt 源码——i 大于 key max(256)即返回 0
本节要点:利用链是“超长输入 → SHA-512 crypt 返回 0 → crypt 返回星号 → 与 shadow 星号 strcmp 相等 → 登录成功”;glibc 系统免疫,musl 是唯一责任方。
五、谁中招、谁安全:默认脆弱的 Alpine 与它的对照组
论点:受影响的只是几种特定配置,但这几种配置恰好是容器世界的默认值。
- 典型靶子:Alpine Linux 的 Docker 镜像默认就处于脆弱配置——密码登录开着、不使用 PAM,默认即可被利用。
- Alpine 情况更糟:其主用户同样中招;Alpine 用 doas 做提权工具(相当于 sudo),doas 等于 sudo、等于 root——一旦登入即等于拿到 root 权限。此外漏洞还带来篡改(tampering)层面的担忧。
- 安全阵营:Debian、Ubuntu、Fedora 等走 glibc 且经 PAM 认证的系统安全;使用 Dropbear 或 OpenBSD 的同样不受影响。
| 阵营 | 配置/系统 | 结果 |
|---|---|---|
| 中招 | musl libc 构建的 OpenSSH,无 PAM、密码认证开启 | 输入超 256 字符密码即可登录 |
| 中招(默认) | Alpine Linux Docker 镜像 | 默认配置即脆弱,doas 直通 root |
| 安全 | glibc + PAM 认证 | Debian、Ubuntu、Fedora 等 |
| 安全 | 其他 SSH 实现 | Dropbear、OpenBSD |
- 范围评估:这几种配置覆盖了互联网相当大的一块,但就配置种类而言受影响范围并不算广——真正危险的不是“面广”,而是“默认即中招”。
图:Alpine 容器演示——默认配置登录后经 doas 直达 root
本节要点:判断自己是否中招只需三问——用不用 musl(Alpine 系)?开没开密码登录?启没启用 PAM?默认脆弱的正是铺天盖地的 Alpine 容器镜像。
六、十年潜伏与“约定漂移”:Rust 也救不了的漏洞
论点:这个漏洞最难防,因为它不属于任何“经典漏洞类型”。
- 排除法:它不是内存破坏漏洞;也不完全是典型逻辑 bug;更不是忘记检查字符串长度(n/n+1、空终止符)那类问题。
- 真正的病灶:两个函数各自持有不同的接口约定,随时间漂移得越来越远——一侧认为“星号永远不可能变成密码哈希”,另一侧认为“crypt 失败时返回什么值无所谓,反正别是 null 就行”。组合效果:允许不存在或无密码的用户去执行加密例程,而该例程在密码超过一定长度时失败,失败级联返回星号,恰好等于锁定用户的哈希,认证被绕过。
- 时间线(论据):漏洞自 2016 年补丁起就存在;Alpine 自 2021 年起中招——因为感叹号"!"会弄坏 PAM,他们把锁定哈希从"!"改成了"*"。到 2026 年披露,整整潜伏十年。
- 离谱的辩护:维护者的理由是“没人会使用 300 位长度的密码”;而攻击者只需要一个有漏洞的版本、一个哈希为星号的账号,再加 300 个 'a'——门槛低到令人发指。
“这可是九二年的黑客水平,疯了。” —— Low Level,评估完攻击门槛后给出的总评
- 延伸问答:用 Rust 能修好这个漏洞吗?其实不能。Rust 的类型、枚举或 Result
/Option 这类显式错误检查手段也许有机会防住它,但从 use-after-free 那种纯内存安全角度看,这个漏洞与之毫无关系——这正是它最有意思的地方。
图:视频结尾讨论——"Rust 能修好吗?其实不能”
本节要点:漏洞无法归入经典类型,是“两个无害约定漂移十年后握手”的产物;“没人会用 300 位密码”的辩护恰恰说明——按正常场景做假设,是安全设计里最贵的错误。
总结要点
- 现象:在 musl libc + 无 PAM + 密码登录开启的配置(Alpine Docker 镜像默认即此)下,输入 257~1024 个字符的“密码”即可登入星号账号,直达 root。
- 成因:1980 年星号=无密码锁定、2012 年 musl crypt 失败返回星号、2016 年反计时攻击补丁让星号账号也真实执行加密——三个各自合理的决定,拼成一条认证绕过链。
- 边界:glibc、启用 PAM、Debian/Ubuntu/Fedora、Dropbear、OpenBSD 均安全;责任在 musl 实现层,OpenSSH 与 musl 已紧急修复,数百万系统需立即打补丁。
- 定性:不是缓冲区溢出、不是内存破坏,而是“接口约定随时间漂移”;潜伏十年(2016→2026),攻击水平被评作“1992 年”。
- 启示:内存安全语言防不住这类漏洞——错误的约定,比错误的内存更隐蔽。
迁移与启发
- 职场协作:把“失败时返回什么”写进约定。 这个漏洞的根源是两个模块各自假设了对方的行为、从未对齐。你给协作方、下属或供应商交代需求时,别只描述“正常流程怎么做”,要明确“出错时你希望看到什么”——报错、兜底还是暂停;一份书面的失败约定,胜过十次“我以为你知道”。
- 投资理财:定期复核“过去十年都成立”的假设。 星号约定 1980 年成立、2012 年悄悄变味、2016 年被放大——投资逻辑也一样:“长期定投必有效”“平台大就不出事”这类隐含前提会随环境漂移。给自己设一张年度复核清单:这条策略依赖的假设现在还成立吗?不成立的就撤,别让旧默契替新环境做决定。
- 生活决策:别用“正常人不会这样”来省略检查。 维护者说“没人会用 300 位密码”,攻击者偏偏就这么干。风控的本质是按最坏情况设计:设账户别沿用默认配置、签合同别假设“对方不会抠条款”、凡是“没人会那么做”的地方,恰恰值得多看一眼。
一句话升华
击穿系统的从来不是最凶猛的炮火,而是两个各自无害的约定在暗处握了手——对代码如此,对人生里那些从未复核过的“默认”亦然。