7-Zip 堆缓冲区溢出:XZ 解码中的长度参数为何可能变成代码执行入口

2026-07-21 22 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

安全研究机构 Zero Day Initiative 报告称,7-Zip 的 MixCoder_Code 函数存在基于堆的缓冲区溢出问题,并给出 7.0 分评级。问题发生在处理 XZ 流的 filter 管道时:解码器得到的是完整输出缓冲区长度,而不是从当前位置算起的剩余空间。这个看似普通的长度计算错误,可能让攻击者借助特制压缩数据越界写入堆内存,并进一步尝试执行任意代码。

漏洞核心:容量不等于剩余空间

假设程序分配了一个 4096 字节的输出缓冲区,前面的 filter 已经写入 3000 字节。此时真正能够继续写入的空间只有 1096 字节:

缓冲区总容量:4096
当前写入位置:3000
实际剩余空间:1096

如果下一级解码器接收到的长度仍然是 4096,它可能从 buffer + 3000 开始继续写入最多 4096 字节。这样一来,写操作就可能越过已分配对象的末尾。

下面是一个用于解释边界计算的简化 C 示例。它不是 7-Zip 源码,也不能用于判断具体版本是否受影响,但能展示这类缺陷的差异:

#include <stddef.h>
#include <stdint.h>

int decode_next(uint8_t *output, size_t capacity, size_t used) {
    if (used > capacity) {
        return -1;
    }

    uint8_t *write_ptr = output + used;
    size_t remaining = capacity - used;

    /* 正确做法:向解码器传递 write_ptr 和 remaining。 */
    return decoder_write(write_ptr, remaining);
}

危险模式则是移动了写指针,却仍传递原始容量:

/* 概念性错误示例 */
return decoder_write(output + used, capacity);

在单层解码、短输出或特定内存布局下,错误未必立刻表现为崩溃。进入多级 filter 管道后,偏移量会随处理过程变化,攻击者便可能构造输入,让某一次写入精确落到缓冲区边界之外。

从越界写到远程代码执行,中间还有条件

“可用于远程执行任意代码”不等于攻击者可以直接通过网络连接控制任何安装了 7-Zip 的机器。通常还需要一条实际的输入路径,例如用户打开或解压不可信压缩包、邮件网关自动检查附件,或者服务端任务调用 7-Zip 处理上传文件。

能否把堆溢出稳定转化为代码执行,还受到以下因素影响:

  • 目标使用的 7-Zip 版本与构建方式;
  • 进程权限以及是否运行在服务账户下;
  • ASLR、DEP/NX、控制流保护等系统缓解机制;
  • 压缩文件进入解码路径的方式;
  • 进程崩溃后是否会被自动重试,以及攻击者是否能重复提交输入。

因此,风险评估不能只检查员工电脑上的桌面程序。更值得优先排查的是自动化处理链:文件上传接口、杀毒扫描前置任务、文档转换服务、CI 构建任务和邮件附件处理器。这些场景会在缺少人工确认的情况下解析外部文件,攻击面通常更直接。

可以这样实践:盘点版本并隔离不可信解压任务

来源摘要没有给出明确的受影响版本范围或修复版本,因此不要仅凭一个版本号自行推断安全状态。应同时核对 7-Zip 官方公告、软件供应商通告以及操作系统发行版的安全更新。

在 Linux 主机上,可以先定位常见命令并记录版本:

set -eu

for cmd in 7zz 7z 7za; do
  if command -v "$cmd" >/dev/null 2>&1; then
    echo "== $cmd =="
    command -v "$cmd"
    "$cmd" i 2>&1 | sed -n '1,8p'
  fi
done

Windows PowerShell 可以这样检查:

$paths = @(
  "$env:ProgramFiles\7-Zip\7z.exe",
  "${env:ProgramFiles(x86)}\7-Zip\7z.exe"
)

foreach ($path in $paths) {
  if (Test-Path $path) {
    $item = Get-Item $path
    [PSCustomObject]@{
      Path = $path
      ProductVersion = $item.VersionInfo.ProductVersion
      FileVersion = $item.VersionInfo.FileVersion
    }
  }
}

在完成升级前,必须处理外部压缩包的 Linux 服务可以把解压步骤放进权限受限的容器。下面的示例假设当前目录包含 Dockerfile、只读输入目录 incoming 和输出目录 unpacked

先创建镜像文件:

FROM alpine:3.20
RUN apk add --no-cache 7zip
RUN addgroup -S extractor && adduser -S -G extractor extractor
USER extractor
WORKDIR /work
ENTRYPOINT ["7z"]

然后构建镜像,并在断网、无额外 capability、限制内存和 CPU 的环境中解压:

mkdir -p incoming unpacked
chmod 700 incoming unpacked

docker build --pull -t isolated-7zip .

docker run --rm \
  --network none \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 512m \
  --cpus 1 \
  --pids-limit 64 \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  -v "$PWD/incoming:/input:ro" \
  -v "$PWD/unpacked:/output" \
  isolated-7zip x /input/sample.7z -o/output -y

运行前把 sample.7z 改成实际文件名,并确保镜像中的软件包已经更新到发行版提供的安全版本。容器隔离只能缩小进程被利用后的权限和可访问资源,不能修复解码器本身;内核漏洞、危险挂载和过宽权限仍可能让隔离失效。

修复时不要漏掉嵌入式调用

企业环境中的 7-Zip 不一定以独立桌面程序出现。某些产品会捆绑 7z.dll、复制命令行程序,或者把解压能力嵌入安装器和后台服务。只升级系统路径中的 7z.exe,未必会替换这些私有副本。

处置时可以按以下清单推进:

  1. 盘点终端、服务器、容器镜像和构建代理中的 7-Zip 可执行文件及动态库。
  2. 根据官方或供应商公告确认受影响范围,并升级到明确包含修复的版本。
  3. 暂停让高权限服务自动解压来源不明的 XZ 或压缩文件。
  4. 将文件解析任务放入低权限、断网、有限资源的沙箱或容器。
  5. 检查解析服务近期的异常退出、堆损坏记录和重复提交的可疑压缩包。
  6. 升级后重新构建基础镜像和应用镜像,避免旧二进制继续留在镜像层中。

这类漏洞再次说明,压缩工具不是无害的文件搬运程序,而是直接解析攻击者可控二进制格式的复杂解析器。补丁是首要措施;权限收缩、输入隔离和资产盘点则决定了下一次解析漏洞出现时,影响会停留在一个失败任务,还是扩展到整台主机。


相关推荐