安全修复 · 2026 年 11 月 11 日

XZ Utils 后门事件:供应链攻击离我们有多近

一个被精心维护了两年的开源项目,在发布压缩包里藏了后门。XZ Utils 事件改写了很多人对供应链的认知,这篇讲它的手法、怎么自查,以及能落地的防御动作。

2024 年 3 月 29 日,PostgreSQL 开发者 Andres Freund 在邮件列表里报告了一件怪事:Debian sid 上 sshd 登录变慢、CPU 异常。他顺着 perf 的调用栈查下去,发现问题不在 OpenSSH,而在 liblzma——XZ Utils 的压缩库。

一个压缩库出现在 sshd 的进程里,本身就不正常。继续查下去,是一段被藏了两年的后门。

这段后门后来被编号为 CVE-2024-3094。它没有利用任何内存漏洞,靠的是构建脚本和人与人之间的信任。

背景:后门是怎么进到发行版里的#

XZ Utils 长期由一位维护者业余维护。2021 年起,一个自称 Jia Tan 的贡献者开始高频提交,逐步获得提交权限,随后又拉来“帮忙”的账号施压,逼走了原维护者。这套操作持续了两年多,是典型的长期社工。

后门没有进 git 仓库,而是藏在发布用的 tarball 里。构建脚本 build-to-host.m4 在打包时被注入,只在特定的打包环境下生效——检测到 dpkg 或 rpmbuild 才展开。从 git clone 编译不会中招,这也是它躲过代码审查的原因。

用一句话概括手法:代码审查看的是 git,发行版打包用的是 tarball,两者在事件里第一次被证明可以不一致。

发现过程也带着偶然。如果这点延迟没被注意到,后门会随下一个发行版进入更多机器。性能问题有时就是安全问题的第一现场。

这类攻击有个共同点:它不需要攻破哪道技术防线,只需要一个人有耐心。代码审查、签名校验、双人复核,防的都是“不信任”,而不是“技术漏洞”。

漏洞原理:构建期注入,运行时挂钩#

点到为止。注入的代码在编译 liblzma 时改写了若干函数,利用 IFUNC 机制在动态链接阶段替换实现。当 sshd 因为 systemd 的通知机制间接加载 liblzma 后,这段代码就能在认证流程里检查客户端证书,让持有特定密钥的人绕过认证直接拿到 shell。

几个设计细节值得记住:

  • 只对 deb/rpm 构建生效,源码构建不受影响;
  • 通过环境变量和构建参数识别目标发行版;
  • 后门代码经过混淆,静态扫描很难发现;
  • 依赖 libsystemd → liblzma 的传递链,属于“合法依赖被滥用”。

这条链的隐蔽性在于:sshd 并没有“链接 xz”,是 systemd 的通知库把它带进来的。只看 sshd 的直接依赖,什么都发现不了。

为什么选 IFUNC?因为它允许在加载时替换函数实现,不需要改调用方代码,也不留明显的符号引用。这类手法在恶意软件里不新鲜,出现在一个被广泛信任的压缩库里,才是真正的问题。

影响范围#

发行版 情况 备注
Debian sid / testing 曾进入 2024 年 3 月的窗口期 已回滚
Fedora 40 / 41 预发布 曾进入 已回滚
openSUSE Tumbleweed 曾进入 已回滚
Debian stable / Ubuntu LTS / RHEL 使用 5.4.x 未受影响

关键判断不是“我用不用 xz”,而是“我用的发行版在窗口期有没有装过 5.6.0 或 5.6.1”。具体的版本与时间窗请以各发行版公告为准。

真正被波及的是滚动发行版和预发布版本。用稳定版的企业环境基本安全,但这也提醒了一件事:CI 里跑 debian:sid 或 fedora:rawhide 的基础镜像,风险等级和生产环境不是一回事。

时间窗也要算清楚。后门在 5.6.0 发布后进入滚动发行版,到 3 月 29 日被发现,中间大约几周。这期间装过系统、更新过包、拉过基础镜像的机器,都在范围内。

检测#

bash
# 1. 版本
xz --version

# 2. 包管理器视角
dpkg -l | grep -E 'liblzma|xz-utils' || rpm -qa | grep -E '^xz|liblzma'

# 3. sshd 是否间接链接了 liblzma
ldd /usr/sbin/sshd 2>/dev/null | grep -i lzma

# 4. 构建脚本残留(打过补丁的包通常没有)
grep -R "build-to-host" /usr/share/doc 2>/dev/null | head

第三条要谨慎解读。很多发行版上 ldd /usr/sbin/sshd 本来就会显示 liblzma(通过 libsystemd),它只能说明“具备被挂钩的条件”,不能证明中招。

真正的判断依据是包版本与发行版公告。第四条只是辅助:如果 /usr/share/doc 下残留了被篡改的构建脚本,值得进一步查。

补充两个容易漏的地方。基础镜像:2024 年 2 到 3 月构建的镜像如果基于 sid 或 testing,要单独核对。vendor 目录:有些项目把 liblzma 源码拷进仓库自己编,这条路径完全绕过了包管理器。

再加一条文件完整性检查:rpm -Va 或 debsums -c 能发现被替换的库文件。输出通常很长,先 grep liblzma 与 xz,再看其余部分。

修复与缓解#

  • 降级到发行版提供的安全版本,通常是 5.4.x 或已回滚的包。不要自己从源码编 5.6.x。
  • 如果机器在窗口期运行过受影响版本,按“可能失陷”处理:轮换 SSH 主机密钥与用户密钥、检查 authorized_keys、审计登录日志。
  • 重建镜像。基于受影响基础镜像构建的镜像要重新构建,而不是在容器里打补丁。
  • 检查构建链:CI 缓存的 tarball、vendor 目录、私有镜像仓库里的旧包。

轮换密钥要分轻重。主机密钥泄露的影响面最大,必须换;用户密钥是否换,取决于机器在窗口期有没有被外部登录过。别一刀切,也别因为怕麻烦就不换。

bash
# Debian/Ubuntu:装回发行版指定版本并确认来源
sudo apt-get update
sudo apt-get install --allow-downgrades liblzma5=5.4.1-0.2   # 版本号以发行版为准
apt-cache policy liblzma5

# 确认无影响后再轮换主机密钥(会让客户端 known_hosts 失效)
sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -A
sudo systemctl restart ssh
注意

换主机密钥是有感的操作:所有客户端都会报 “REMOTE HOST IDENTIFICATION HAS CHANGED”。先想清楚是否真的需要换——只有确认机器在窗口期运行过受影响版本,才值得付这个代价。

验证#

  • xz --version 与 apt-cache policy 一致,且等于发行版的安全版本。
  • dpkg -V liblzma5 或 rpm -V xz-libs 无输出,说明已安装文件未被改动。
  • 用一台外部机器 SSH 登录,确认新主机指纹符合预期,并同步给使用方。
  • 复查窗口期内的登录日志,看有没有来自陌生来源的成功登录;有条件的做一次流量回溯。
  • 确认 CI 流水线里不再出现从 tarball 直接安装的 xz。

结论#

  • 这次攻击的关键不在技术,而在“人”:花两年时间换一个提交权限。
  • tarball 与 git 不一致是盲区,构建产物要能追溯到源码。
  • 自查只需三步:版本、来源、窗口期。
  • 窗口期内运行过受影响版本的机器,按失陷处理,别只降级了事。
  • 长期动作:可复现构建、依赖锁定、对关键依赖做人工复核。

完

#供应链#XZ#开源安全
回到顶部 ↑