xz-utils 供应链投毒事件:构建系统投毒与 SSH 认证后门的完整攻击链

举报
yd_278325498 发表于 2026/11/07 09:01:08 2026/11/07
【摘要】 xz-utils 供应链投毒事件:构建系统投毒与 SSH 认证后门的完整攻击链2024 年 3 月 29 日,一位微软工程师在排查 SSH 性能问题时,意外揭开了一场潜伏近三年的供应链投毒攻击。这不是一次普通的漏洞披露——它是一场教科书级别的长期社会工程 + 构建系统投毒 + 运行时后门注入的完整攻击链。本文将逐层拆解这条链的每一个环节。 一、事件全景:一条时间线时间事件2021 年初账号...

xz-utils 供应链投毒事件:构建系统投毒与 SSH 认证后门的完整攻击链

2024 年 3 月 29 日,一位微软工程师在排查 SSH 性能问题时,意外揭开了一场潜伏近三年的供应链投毒攻击。这不是一次普通的漏洞披露——它是一场教科书级别的长期社会工程 + 构建系统投毒 + 运行时后门注入的完整攻击链。本文将逐层拆解这条链的每一个环节。


一、事件全景:一条时间线

时间 事件
2021 年初 账号 “Jia Tan”(JiaT75)开始在 xz-utils 项目中提交代码,修复 bug、参与讨论
2022 年 Jia Tan 逐渐成为项目核心贡献者,提交大量高质量补丁
2023 年 原维护者 Lasse Collin 因个人原因逐渐减少参与,Jia Tan 获得更多控制权
2024 年 2 月 Jia Tan 获得提交权限(commit access),成为实际维护者
2024 年 2-3 月 恶意代码通过 release tarball(发布压缩包)注入,但 Git 仓库中不可见
2024 年 3 月 29 日 微软工程师 Andres Freund 发现 sshd 异常延迟,追根溯源到 xz-utils
2024 年 3 月 29 日 Red Hat、Debian、Ubuntu 等发行版紧急回滚到 5.4.6 版本
2024 年 3 月 30 日 CVE-2024-3094 分配,CVSS 评分 10.0(最高级别)

关键数字:CVSS 10.0、影响 Debian/Ubuntu/Kali 等多个主流发行版的 testing/unstable 分支、后门代码隐藏在"测试文件"中、Git 仓库中完全不可见。


二、攻击者画像:Jia Tan 是谁?

这是整条链中最令人不寒而栗的部分——攻击者没有使用任何技术漏洞来获取权限,而是用耐心和信任打开了大门。

2.1 社会工程策略

Jia Tan 的操作手法极其克制:

  • 不急于求成:从 2021 年首次提交到 2024 年植入后门,跨度近三年。
  • 先建立信任:前期提交的都是真实有效的 bug 修复和功能改进,代码质量高,态度专业,积极参与邮件列表讨论。
  • 利用维护者疲劳:原维护者 Lasse Collin 公开表示过维护压力和倦怠。Jia Tan 适时"伸出援手",主动承担更多维护工作,最终顺理成章地获得了 commit access。
  • 身份无法追溯:“Jia Tan” 没有可验证的真实身份,没有可追溯的雇主信息,时区行为分析暗示其可能并非其所声称的身份。

2.2 为什么这比技术漏洞更危险

传统的安全防御关注代码层面的漏洞——SQL 注入、缓冲区溢出、权限提升。但 Jia Tan 的攻击绕过了所有这些防御,因为:

  • 代码审查工具(CodeQL、Semgrep 等)扫描的是 Git 仓库中的代码,而恶意代码不在 Git 中。
  • 信任模型基于"贡献者历史",而 Jia Tan 的历史是干净且高质量的。
  • 发行版的打包流程信任上游 release tarball,而 tarball 与 Git 仓库内容不一致。

核心教训:开源安全的信任链,归根结底建立在"人"的信任上。当这个信任被系统性地操纵时,所有技术防御都会失效。


三、技术拆解:后门是如何工作的?

这是整条链中最精妙(也最危险)的部分。后门的注入分为三个阶段:构建时注入、加载时劫持、运行时后门。

3.1 阶段一:构建时注入——隐藏在"测试文件"中的恶意载荷

3.1.1 投毒的文件

xz-utils 的 release tarball(由 Jia Tan 打包发布的版本)中包含以下"测试文件":

tests/files/bad-3-corrupt_lzma2.xz
tests/files/good-large_compressed.lzma

这些文件看起来是正常的测试数据——二进制 blob,用于测试 xz 解压缩器的鲁棒性。它们存在于 Git 仓库中,看起来人畜无害。

但 release tarball 中的版本与 Git 仓库中的版本不同。 Jia Tan 在打包 release 时,替换了这些文件的内容。这就是为什么只看 Git 仓库的人什么也发现不了。

3.1.2 混淆层:二进制文件中的隐藏脚本

这些"测试文件"实际上是经过精心构造的。它们包含嵌入的 shell 脚本和二进制数据,通过一个极其复杂的混淆机制来隐藏真实意图。

提取过程大致如下:

  1. build-to-host.m4(一个构建系统宏文件,也在 release tarball 中被替换)在配置阶段执行。
  2. 该宏文件会读取 tests/files/bad-3-corrupt_lzma2.xz,从中提取隐藏的脚本。
  3. 提取过程使用 xz 自身的解压功能,配合特定的字节偏移和异或操作,从看似随机的二进制数据中"拼"出可执行的 shell 脚本。
# 简化的提取逻辑示意(实际远比这复杂)
# bad-3-corrupt_lzma2.xz 中隐藏的脚本片段
# 通过特定偏移读取 → xz 解压 → 拼接 → 得到完整脚本

3.1.3 条件触发:只在特定环境激活

后门不是无条件激活的。build-to-host.m4 中包含一系列环境检测:

# 检测条件(简化示意)
if test "$(arch)" = "x86_64" \       # 只在 x86_64 架构激活
   && test "$(uname)" = "Linux" \    # 只在 Linux 上激活
   && ... # 检查是否在构建 deb/rpm 包
   && ... # 检查是否使用特定编译器
then
    # 执行注入
fi

这意味着:

  • 从源码直接编译(如 Gentoo)不会触发后门。
  • 非 x86_64 架构(如 ARM、RISC-V)不会触发后门。
  • 只有通过发行版打包流程(deb/rpm)在 x86_64 Linux 上构建时,后门才会被激活。

这是一个极其聪明的选择——它精准瞄准了最终用户最可能安装的发行版二进制包,同时避免了在源码审查和 CI/CD 环境中被发现。

3.2 阶段二:加载时劫持——IFUNC 机制与 liblzma → sshd 的桥梁

3.2.1 后门注入 liblzma

构建脚本提取出的恶意载荷最终被编译进 liblzma.so(xz-utils 的核心共享库)。注入方式利用了 GNU 间接函数(IFUNC)机制:

// 简化示意:IFUNC 解析器被劫持
// 正常流程:liblzma 的 IFUNC 解析器在加载时选择最优的函数实现
// 被篡改后:解析器在执行正常逻辑的同时,额外执行后门初始化代码

__attribute__((ifunc("resolver")))
static void (*lzma_crc64_resolve(void))(void) {
    // 正常的函数选择逻辑...

    // 隐藏的后门初始化(被混淆,不易被静态分析发现)
    backdoor_init();  // 实际名称被混淆

    return optimal_implementation;
}

3.2.2 从 liblzma 到 sshd:systemd 的间接依赖

这是整条链中最关键的"桥梁"环节。后门最终要攻击的是 SSH 服务器(sshd),但 xz-utils 和 SSH 看似毫无关系。连接路径如下:

sshd (OpenSSH)
  └─ 链接 libsystemd(用于 systemd 通知集成)
       └─ libsystemd 链接 liblzma(用于日志压缩)
            └─ liblzma 被注入后门

具体来说:

  1. Debian/Ubuntu 上的 OpenSSH 被编译为支持 systemd 通知(--with-systemd)。
  2. 因此 sshd 会链接 libsystemd.so。
  3. libsystemd.so 会链接 liblzma.so(用于压缩日志等)。
  4. 当 sshd 启动时,动态链接器加载 liblzma.so,触发 IFUNC 解析器,执行后门初始化代码。

后门通过这条依赖链,在 sshd 进程内获得了代码执行权。

3.2.3 RSA_public_decrypt 劫持

后门初始化后,会劫持 OpenSSL 的 RSA_public_decrypt 函数。这是 SSH 认证过程中用于验证 RSA 签名的核心函数。

// 劫持示意
original_RSA_public_decrypt = dlsym(RTLD_NEXT, "RSA_public_decrypt");

int hooked_RSA_public_decrypt(...) {
    // 检查传入的 RSA 密钥/签名中是否包含后门指令
    if (contains_backdoor_payload(sig, siglen)) {
        execute_backdoor_command(extract_command(sig, siglen));
        return 1;  // 伪装为认证成功
    }
    // 否则调用原始函数,正常验证
    return original_RSA_public_decrypt(...);
}

3.3 阶段三:运行时后门——SSH 认证绕过与远程命令执行

3.3.1 后门认证机制

后门不是简单的"万能密码"。它使用了一个极其精密的加密认证方案:

  1. 攻击者持有一个 Ed448 私钥(后门中嵌入了对应的公钥)。
  2. 攻击者构造一个特殊的 SSH 证书/签名,其中包含:
    • 用 Ed448 私钥签名的命令载荷。
    • 载荷被编码到 RSA 签名的特定字段中。
  3. 当这个特殊的"认证请求"到达被后门感染的 sshd 时:
    • 劫持的 RSA_public_decrypt 检测到特殊载荷。
    • 用嵌入的公钥验证 Ed448 签名。
    • 验证通过后,提取并执行载荷中的命令。
    • 返回认证成功,攻击者获得 shell 访问权。

3.3.2 后门能力

一旦激活,后门提供以下能力:

  • 任意命令执行:攻击者可以在被感染的主机上以 root 权限执行任意命令(因为 sshd 通常以 root 运行)。
  • 隐蔽性:后门命令不会出现在正常日志中。认证成功记录可以被伪造为正常的密钥认证。
  • 持久性:只要 liblzma 被加载(即 sshd 运行),后门就持续有效。
  • 选择性激活:后门只对包含正确 Ed448 签名的请求响应,对普通 SSH 连接完全透明——不会产生任何异常行为或性能影响(这也是为什么它潜伏了这么久才被发现)。

3.3.3 为什么性能问题暴露了后门

发现者 Andres Freund 注意到 sshd 在某些情况下有约 500ms 的异常延迟。这个延迟并非后门本身造成,而是后门在处理 RSA_public_decrypt 劫持时的一个微小性能开销——后门代码在每次调用时都会检查输入是否包含后门载荷,这个检查在特定条件下会产生可测量的延迟。

讽刺的是:如果后门作者在这个性能细节上再小心一点,后门可能还会潜伏更久。


四、攻击链全景图

┌─────────────────────────────────────────────────────────────────┐
│                    第一阶段:社会工程(2021-2024)                │
│                                                                 │
│  Jia Tan 加入项目 → 提交高质量代码 → 获得信任 → 获得 commit 权限  │
│         → 等待原维护者减少参与 → 成为实际维护者                   │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                 第二阶段:构建系统投毒(2024.2-3)                │
│                                                                 │
│  替换 release tarball 中的"测试文件"和 build-to-host.m4         │
│         ↓                                                       │
│  测试文件中隐藏混淆的 shell 脚本                                │
│         ↓                                                       │
│  build-to-host.m4 在 configure 阶段提取并执行脚本              │
│         ↓                                                       │
│  环境检测:x86_64 + Linux + deb/rpm 打包 → 满足条件才继续       │
│         ↓                                                       │
│  恶意载荷编译进 liblzma.so                                      │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                 第三阶段:加载时劫持(sshd 启动时)              │
│                                                                 │
│  sshd 启动 → 加载 libsystemd → 加载 liblzma                     │
│         ↓                                                       │
│  liblzma IFUNC 解析器触发后门初始化                             │
│         ↓                                                       │
│  劫持 RSA_public_decrypt 函数                                   │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│                 第四阶段:运行时后门(攻击者连接时)             │
│                                                                 │
│  攻击者发送包含 Ed448 签名的特殊 SSH 认证请求                   │
│         ↓                                                       │
│  劫持的 RSA_public_decrypt 检测到后门载荷                       │
│         ↓                                                       │
│  验证 Ed448 签名 → 提取命令 → 以 root 权限执行                  │
│         ↓                                                       │
│  返回认证成功 → 攻击者获得 shell → 日志中无异常痕迹              │
└─────────────────────────────────────────────────────────────────┘

五、为什么现有防御体系全部失效?

5.1 代码审查

  • Git 仓库是干净的。恶意代码只存在于 release tarball 中,任何基于 Git 仓库的代码审查(包括 GitHub 的 CodeQL、人工 review)都看不到恶意代码。
  • 测试文件是二进制 blob。即使有人审查 release tarball,二进制测试文件也不会引起怀疑——它们"本来就该是乱码"。

5.2 漏洞扫描器 / 静态分析

  • 后门代码经过高度混淆,使用多层解压和异或操作来隐藏真实逻辑。
  • 恶意代码以 shell 脚本形式嵌入二进制文件,静态分析工具不会解析这种格式。
  • IFUNC 机制本身就是合法的编译器特性,劫持 IFUNC 解析器不会触发常规的函数劫持检测。

5.3 包签名 / 发行版验证

  • Release tarball 由维护者(Jia Tan)签名,签名验证通过——因为签名密钥本身就在攻击者手中。
  • 发行版的打包流程信任上游签名,不会对比 tarball 与 Git 仓库的内容差异。

5.4 运行时检测

  • 后门对普通 SSH 连接完全透明,不产生异常日志、不修改已知文件、不建立异常网络连接。
  • 后门只在收到特定 Ed448 签名请求时激活,平时处于完全静默状态。
  • 唯一的异常是微小的性能延迟——而 500ms 的延迟在 SSH 连接中很容易被归因于网络问题。

5.5 防御失效总结

防御层 失效原因
Git 代码审查 恶意代码不在 Git 中
Release tarball 审查 恶意代码隐藏在二进制测试文件中
静态分析 多层混淆 + 非标准载荷格式
包签名验证 签名密钥在攻击者手中
Git 仓库 vs tarball 对比 无人执行此对比
运行时监控 后门静默,无异常行为
网络监控 后门复用 SSH 端口,无新连接
日志审计 后门伪造认证日志

六、如果没被发现,后果有多严重?

6.1 影响范围

  • xz-utils 5.6.0 和 5.6.1 已经进入 Debian testing、Ubuntu 23.10 (Mantic) 和 23.04 (Lunar) 的仓库。
  • 如果再晚几天,它将进入 Debian stable 和 Ubuntu 24.04 LTS(长期支持版)的候选列表。
  • Ubuntu 24.04 LTS 是大量生产服务器的首选操作系统——一旦后门进入 LTS,影响将持续至少 5 年。

6.2 潜在受害目标

任何运行被感染 sshd 的服务器都是潜在目标,包括但不限于:

  • 云服务器(AWS EC2、Azure VM、GCP Compute Engine 等)
  • 企业内部服务器
  • CI/CD 构建机器(攻击者可以进一步污染更多软件供应链)
  • 政府和军事系统
  • 科研机构的高性能计算集群

6.3 级联效应

如果后门进入 CI/CD 系统,攻击者可以:

  1. 在构建机器上获得 root 权限。
  2. 修改构建产物,植入更多后门。
  3. 被感染的新版本软件继续感染更多 CI/CD 系统。
  4. 形成供应链感染的指数级扩散。

七、我们能学到什么?

7.1 对开源项目维护者

  • 不要让单一人控制整个项目。至少需要两人以上的 commit 权限,且关键操作需要多人审批。
  • Release tarball 应该可复现构建。如果 tarball 可以从 Git 仓库通过确定性构建流程生成,就不存在"tarball 与 Git 不一致"的攻击空间。
  • 警惕"过于热心"的新贡献者。尤其是那些在维护者疲惫时主动承担大量工作的贡献者——虽然大多数是善意的,但 xz-utils 事件证明了攻击者会利用这种心理。
  • 对二进制文件保持警惕。仓库中的二进制文件(测试数据、预编译产物等)是天然的盲区,应有专门的审查流程。

7.2 对发行版和包管理器

  • 验证 tarball 与 Git 仓库的一致性。Debian 和 Fedora 已经在事件后开始实施这一检查——如果 tarball 内容与对应 Git tag 的构建产物不一致,拒绝打包。
  • 减少不必要的依赖链。sshd 依赖 libsystemd 依赖 liblzma 这条链,在事件后被重新审视。OpenSSH 项目本身不依赖 liblzma,是发行版编译选项引入的间接依赖。
  • 对关键包实施更严格的准入流程。xz-utils 被数以万计的包依赖,是"关键基础设施"级别的项目,应该有更高级别的审查。

7.3 对安全工程师

  • 关注构建系统本身。configure 脚本、m4 宏、Makefile——这些是安全审查的盲区,但它们在构建时拥有完整的代码执行权限。
  • 监控共享库的 IFUNC / GOT 修改。IFUNC 劫持是一种合法但可被滥用的机制,运行时检测工具应该覆盖这一向量。
  • 重视性能异常作为安全信号。Andres Freund 的发现始于对 500ms 延迟的好奇心——性能异常可能是安全事件的最早信号。
  • 建立 reproducible build 验证。可复现构建是验证"二进制产物是否对应源码"的最根本手段。

7.4 对所有人

  • 开源不等于安全。“given enough eyeballs, all bugs are shallow” 这句话在 xz-utils 事件面前显得苍白——恶意代码不在 Git 中,eyeballs 看不到它。
  • 信任是需要持续验证的。一个贡献者三年的"良好行为"不能证明其善意——它可能只是在为最终的攻击做铺垫。
  • 最危险的攻击不一定是技术最复杂的。Jia Tan 的社会工程部分没有任何技术难度,但它是最有效的部分。

八、事件后续

8.1 技术响应

  • xz-utils 回滚到 5.4.6 稳定版本。
  • 多个发行版发布了安全公告和修复包。
  • GitHub 暂停了相关账号。
  • 安全研究人员对 xz-utils 的历史提交进行了全面审计,以确认是否有更早的植入。

8.2 社区响应

  • OpenSSF(开源安全基金会)发起了"可复现构建"推广计划。
  • 多个项目开始实施"两人规则"(two-person rule)的提交审批。
  • Debian 和 Fedora 开始实施 tarball 与 Git 一致性检查。
  • 关于开源项目维护者倦怠和可持续性的讨论显著增加——维护者需要合理的报酬和支持,否则"Jia Tan 们"会继续趁虚而入。

8.3 未解之谜

截至本文撰写时,Jia Tan 的真实身份仍未确定。调查涉及多个国家的执法机构。这个账号背后的真实人(或团队)可能永远不会被找到。


九、结语

xz-utils 事件不是一个孤立的安全漏洞——它是一种新的攻击范式的首次大规模实战演示。这种攻击范式不利用任何内存安全漏洞、不依赖任何零日漏洞,而是系统性地操纵开源软件的信任模型。

攻击者用三年时间证明了一件令人不安的事:我们的软件供应链建立在信任之上,而信任是可以被欺骗的。

这不是最后一个 xz-utils 式攻击。每一个依赖单一维护者、缺乏可复现构建、不验证 tarball 完整性的开源项目,都是下一个潜在目标。

我们能做的,不是恐慌,而是从这条攻击链的每一个环节吸取教训,逐一加固。社会工程 → 构建投毒 → 加载劫持 → 运行时后门,每一个环节都有对应的防御措施。问题不在于我们不知道怎么防御,而在于我们是否愿意在"没出事"的时候投入资源去做这些"看起来多余"的事。

“信任是安全的基础,而信任需要验证。” —— xz-utils 事件留给我们的最重要的一课。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。