CVE-2024-47611 — XZ Utils Windows CRT 最佳适配参数注入与目录遍历¶
发布于 2026-06-06
已验证的复现
本页面记录了对 CVE-2024-47611 的确认利用。越出工作目录的文件写入已通过独立渠道观测,并在补丁版本二进制文件上确认不存在该行为。
| 字段 | 详情 |
|---|---|
| Project | XZ Utils |
| 受影响组件 | xz.exe、xzdec.exe、lzmadec.exe、lzmainfo.exe——仅限 Windows 原生构建 |
| Severity | MEDIUM |
| CVSS | CVSS v4.0:6.3(Medium) |
| CWE | CWE-88, CWE-176 |
| 受影响版本 | XZ Utils ≤ 5.6.2,仅限 Windows 原生构建(MinGW-w64 或 MSVC) |
| 修复版本 | 5.6.3(发布于 2024-10-01) |
| 安全公告 | GHSA-m538-c5qw-3cg4 |
1. 漏洞概述¶
XZ Utils 是常用的命令行压缩工具集,附带 liblzma 库,支持 Linux、macOS 和 Windows 平台。CVE-2024-47611 仅影响 Windows 原生可执行文件(即使用 MinGW-w64 或 MSVC 构建的版本),Cygwin 和 MSYS2 变体以及 liblzma 库本身不受影响。只要攻击者能控制传入受影响工具的文件名参数,该工具就会读写其启动目录之外的路径。在使用传统 Windows 代码页的系统上,一个构造好的文件名即可突破工具本不应访问的目录边界。
根本原因¶
Windows 运行时负责将 Unicode 命令行转换为 main() 接收的 argv[] 数组。在 XZ Utils 5.6.3 之前,Windows 可执行文件构建时未嵌入声明 activeCodePage = UTF-8 的应用程序清单(manifest)。缺少该声明时,Windows 启动代码回退到系统当前激活的传统代码页(例如 Windows-1252),并在转换过程中应用"最佳适配"(best-fit)字符映射——当某个 Unicode 字符无法直接编码到传统代码页时,Windows 静默地以外观最接近的 ASCII 字符替代。
以下几个 Unicode 字符会被映射为在命令行解析或路径处理中具有特殊含义的 ASCII 元字符:
- U+2215 DIVISION SLASH(
∕,UTF-8 字节e2 88 95)映射为 ASCII/(0x2F)——从而实现目录遍历。 - 形似双引号的字符映射为
"(0x22)——破坏参数引号边界,注入额外参数。 - 形似问号的字符映射为
?(0x3F)——触发通配符展开。
补丁(提交 bf518b9ba446327a062ddfe67e7e0a5baed2394f)通过新增 src/common/w32_application.manifest 文件并将其接入 src/common/common_w32res.rc,将 Windows 应用程序清单嵌入到每个可执行文件中:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
</windowsSettings>
</application>
该清单条目指示 Windows 加载器在命令行到 argv[] 的转换中使用 UTF-8(在 Windows 10 版本 1903 及更高版本上可用)。有了该清单,U+2215 会原样传递给 main()——不会被改写为 /——因此不会发生遍历。资源文件仅为非 Cygwin/MSYS2 的 Windows 应用程序构建条件性地包含该清单:
#if MY_TYPE == VFT_APP && !defined(__CYGWIN__)
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "w32_application.manifest"
#endif
旧版 Windows
activeCodePage=UTF-8 设置仅在 Windows 10 版本 1903 及更高版本上生效。在较旧 Windows 版本上运行的二进制文件不受此修复保护。
2. 漏洞复现环境¶
复现环境是单个 Docker 容器(linux/amd64)。容器使用 MinGW-w64 工具链将受漏洞影响和已打补丁两个版本的 XZ Utils 交叉编译为 Windows PE 可执行文件,再通过 Wine 运行。
容器内包含的内容:
/opt/xz-vuln/bin/xz.exe——XZ Utils 5.6.2,从未修改的xz-5.6.2.tar.gz编译,无 UTF-8 清单。这是存在漏洞的二进制文件。/opt/xz-patched/bin/xz.exe——XZ Utils 5.6.3,从未修改的xz-5.6.3.tar.gz编译,已嵌入清单。这是用于差分对照的已修补二进制文件。- Wine 前缀在启动时配置为
en_US.CP1252区域设置,使 Windows 活动代码页(ACP)设为 1252(Windows-1252),从而令 CRT 最佳适配 argv 映射对漏洞版本二进制文件生效。 /lab/work——启动xz的受限工作目录。/lab/outside——越出工作目录的同级目录,在未进行../遍历的情况下无法从/lab/work到达。容器入口脚本在每次启动时清空该目录,为每次利用运行建立空目录基线。
下载完整环境:env.zip
各独立文件:
启动环境:
验证环境配置正确——以下冒烟测试用于确认两个二进制文件的版本、传统代码页以及目录结构:
docker exec cve-2024-47611-xz sh -c '
wine /opt/xz-vuln/bin/xz.exe --version | grep -i "5.6.2" &&
wine /opt/xz-patched/bin/xz.exe --version | grep -i "5.6.3" &&
wine reg query "HKLM\\System\\CurrentControlSet\\Control\\Nls\\CodePage" /v ACP | grep -i 1252 &&
test -d /lab/work && test -d /lab/outside &&
echo SMOKE-OK'
预期输出应包含 5.6.2、5.6.3、一行 ACP ... 1252 以及 SMOKE-OK。Wine 可能在 stderr 输出无害的 fixme/err 行,可忽略。
3. 利用方式¶
利用程序向存在漏洞的 xz.exe 传入一个构造好的文件名:用 U+2215 DIVISION SLASH(∕,UTF-8 字节 e2 88 95)替换路径 ../outside/payload 中的每个 /。由于 ACP=1252 处于激活状态,Windows CRT 在调用 main() 之前把每个 U+2215 改写为 ASCII /,xz 实际处理的路径因此变为 ../outside/payload,从受限的 /lab/work 爬升到 /lab/outside,并将压缩输出 ../outside/payload.xz 写入该目录。
下载利用程序:exploit.zip — exploit/run.sh
步骤一——将利用脚本复制到容器中¶
步骤二——对漏洞版本二进制文件运行利用程序¶
docker exec -w /lab/work cve-2024-47611-xz sh /tmp/run.sh /opt/xz-vuln/bin/xz.exe /lab/work outside payload "PWNED-BY-CVE-2024-47611"
run.sh 在内部使用 POSIX 八进制转义 \342\210\225(对应 U+2215 的三个 UTF-8 字节)将构造好的文件名组装为 .. + U+2215 + outside + U+2215 + payload。脚本首先在 /lab/outside/payload 处写入攻击者的明文输入文件(这是攻击者的输入内容,不是证明目标),随后调用 wine xz.exe -k -v <crafted_filename>。-k 标志保留输入文件,-v 使 xz 输出包含解析路径的进度信息。
脚本在调用 xz 之前打印原始参数字节,以便确认 U+2215 字节存在且 Shell 未引入 ASCII 斜杠:
位于第 3–5 和 13–15 字节处的 e2 88 95 是 U+2215 的字节——并非 ASCII / 的 2f。
随后 xz 的 stderr 显示最佳适配改写已生效:
输出路径中出现了真实的 ASCII /,而非 ∕。参数在到达 main() 时已被改写。
步骤三——通过独立渠道观测越出目录的文件¶
证明不依赖 xz 自身的 stdout。验证方直接从宿主侧 Shell 读取文件系统,完全不经过利用进程:
docker exec cve-2024-47611-xz sh -c 'ls -la /lab/outside; find /lab/outside -type f -exec sh -c "echo FILE: \$1; od -An -tx1 \"\$1\" | head -2" _ {} \;'
在已验证的运行中观测到的结果:
- 出现了新文件
/lab/outside/payload.xz——88 字节,起始字节为fd 37 7a 58 5a 00——即 XZ 容器的魔数头部。只有xz才能在该路径生成有效的 XZ 容器。 /lab/work(受限启动目录)为空——xz未在其中写入任何内容;输出完全逸出到/lab/outside。- 解压证明目标文件确认了标记内容:
xz -dc /lab/outside/payload.xz→PWNED-BY-CVE-2024-47611(退出码 0)。
差分对照——补丁版本二进制文件不发生遍历¶
使用完全相同的调用方式针对已打补丁的二进制文件运行,可确认该效果与缺少清单直接相关:
docker exec -w /lab/work cve-2024-47611-xz sh /tmp/run.sh /opt/xz-patched/bin/xz.exe /lab/work outside payload "PWNED-BY-CVE-2024-47611"
观测到的结果:
- 传入 xz 的参数字节与之前完全相同(
... e2 88 95 ...)。 - xz stderr 输出:
xz: ..∕outside∕payload: No such file or directory——U+2215 保持原样,解析路径中未出现 ASCII 斜杠,未发生遍历。 - 补丁版本运行后通过独立渠道检查:
/lab/outside中仅包含预置的payload输入文件,payload.xz不存在。遍历行为可证明地绑定到未打补丁的二进制文件。
环境清理¶
4. 安全建议¶
修复措施¶
升级至 XZ Utils 5.6.3 或更高版本。5.6.3 在每个受影响的可执行文件中嵌入了 activeCodePage=UTF-8 Windows 应用程序清单,在 Windows 10 版本 1903 及更高版本上阻止 Windows CRT 在命令行解析时应用最佳适配字符替换。
若无法立即升级,以下缓解措施可降低风险:
- 避免将不受信任的文件名传入 Windows 上的
xz.exe、xzdec.exe、lzmadec.exe或lzmainfo.exe。 - 在 UTF-8 系统代码页下运行。 在 Windows 10 1903 及更高版本上,系统级的 UTF-8 测试版设置(
控制面板 → 区域 → 管理 → 更改系统区域设置 → 测试版:使用 Unicode UTF-8 提供全球语言支持)同样可以禁用最佳适配映射,即使没有清单也能生效。此为系统级变更,应在充分评估上下文后再行应用。 - Cygwin 和 MSYS2 构建的参数编码处理方式不同,不受此漏洞影响。对于能够接受该变更的环境,切换到上述工具链变体是一个可行选项。
- 无论是否嵌入清单,该修复在 Windows 10 1903 之前的版本上均不生效。在旧版 Windows 上,应避免将攻击者可控的文件名传入这些工具。
参考资料¶
- GHSA-m538-c5qw-3cg4 — GitHub 安全公告——受影响/已修复版本、CWE 分类、CVSS 评分、发现者(DEVCORE Research Team 的 Orange Tsai 和 splitline)
- NVD CVE-2024-47611——CWE 分类、描述、参考资料
- 补丁提交 bf518b9 — tukaani-project/xz——完整差分;根本原因分析文档
- XZ Utils v5.6.3 发布说明——官方更新日志,说明安全修复及其适用范围
- BUseclab/cve-genie exploit.py——使用 Wine 和 U+2215 替代字符的社区概念验证脚本