FlawOpen

flawopen.com/路径遍历/Zip Slip

什么是 Zip Slip 漏洞?

Reference page
ELI5

ZIP 文件内部可以包含 ../../outside.txt 这样的名称。如果解压程序盲目信任名称,文件就可能写到目标目录之外;范围受进程权限限制。

为什么容易被忽略

解压看起来很普通,但不可信 ZIP 或 TAR 中的每个条目名称都由创建者控制。未经验证就拼接 outputDir + entry.name,会让攻击者通过归档内容跳出目录。

VULNERABLE (conceptual)
for entry in zip_file:
    dest = os.path.join(output_dir, entry.name)
    write_file(dest, entry.read())
# entry.name 可能是 "../../outside.txt"
仅做词法路径限制 — 单独使用无法防止竞态
root = os.path.realpath(output_dir)
os.makedirs(root, exist_ok=True)
for entry in zip_file:
    dest = os.path.realpath(
      os.path.join(root, entry.name)
    )
    try:
        inside_root = os.path.commonpath([root, dest]) == root
    except ValueError:
        inside_root = False
    if not inside_root:
        raise ValueError("Unsafe entry path")
    write_file(dest, entry.read())

此检查可阻止路径逃逸,包括共享字符串前缀的相邻路径。请解压到新建的私有目录,拒绝符号链接条目,并考虑已有符号链接或并发文件系统修改;这些情况需要具备抗竞态能力的文件系统 API 或维护良好的解压库。

常见问题

Python 的归档 API 对路径有不同保护

上面的代码对是自定义解压循环的概念示例,并非 Python 内置的 ZipFile.extractall()。Python 文档说明,ZipFile.extract() 和 extractall() 会尝试阻止绝对路径和 .. 导致的目录逃逸,但仍警告必须先检查不可信归档。zipfile.Path 不会清理成员名称,调用方必须自行验证。TAR 提取过滤器在 Python 3.12 中加入;Python 3.14 将默认值改为 filter="data"。在支持该功能且默认值可能较宽松的运行时中,应显式设置此过滤器。即使是 data 过滤器,也无法阻止所有不安全行为或资源耗尽风险。请解压到新建的私有临时目录,并限制归档大小、成员数量、解压后的总字节数和处理时间。

现代归档库会自动防护吗?

部分版本在这种模式于 2018 年广泛披露后加入了防护;但仍有许多版本默认不验证路径。请查阅所用版本的文档,并显式验证每个条目。

参考资料

另请参阅 路径遍历(CWE-22):原理及在各种语言中的防范 — flawopen.com Node.js path.join() 无法阻止路径穿越 PHP include() 与用户控制的路径 修复 Python os.path.join() 路径穿越