Python Zip64定位器偏移漏洞分析与验证

本文详细分析了Python zipfile模块中的Zip64定位器偏移漏洞,该漏洞导致Python解析特殊构造的zip文件时返回与其他实现不同的内容,可能被用于隐藏恶意代码。

漏洞概述

受影响版本

  • Python 3.12.9
  • Python 3.13.5

已修复版本

漏洞描述

摘要

可以构造一个zip文件,当被Python的zipfile实现解析时,会返回与其他常见zip实现不同的内容。这是因为Python忽略了Zip64定位器记录中的偏移量。相反,Python的实现期望在Zip64定位器记录之前立即看到Zip64端部中央目录记录,并完全忽略偏移量。

这意味着可以存在两个Zip64端部中央目录记录:一个由Zip64定位器记录中的偏移量指向,另一个位于Zip64定位器记录之前。

严重性

中等 - 此漏洞可被利用来隐藏逃避检测的恶意内容。

概念验证

单文件Zip

以下base64编码字符串是一个特殊构造的zip文件,作为简单的概念验证:

1
$ echo "UEsDBBQAAAAAAAAAIQBLlVV3CwAAAAsAAAALAAAAYm9yaW5nX2ZpbGVub3QgcHl0aG9uClBLAQIUAxQAAAAAAAAAIQBLlVV3CwAAAAsAAAALAAAAAAAAAAAAAAC0AQAAAABib3JpbmdfZmlsZVBLBgYsAAAAAAAAAC0ALQAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAA5AAAAAAAAADQAAAAAAAAAUEsDBBQAAAAAAAAAIQBh7IWUCgAAAAoAAAAHAAAAcHlfZmlsZWlzIHB5dGhvbgpQSwECFAMUAAAAAAAAACEAYeyFlAoAAAAKAAAABwAAAAAAAAAAAAAAtAGlAAAAcHlfZmlsZVBLBgYsAAAAAAAAAC0ALQAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAA1AAAAAAAAANQAAAAAAAAAUEsGBwAAAABtAAAAAAAAAAEAAABQSwUGAAAAAAEAAQA5AAAANAAAAAAA" | base64 -d > poc.zip

使用Python解压:

1
2
3
4
5
6
$ mkdir ~/py && cd ~/py
$ python3 -c "import zipfile; zipfile.ZipFile('../poc.zip').extractall()"
$ ls
py_file
$ cat py_file
is python

使用unzip(InfoZip)解压:

1
2
3
4
5
6
$ mkdir ~/unzip && cd ~/unzip
$ unzip ../poc.zip
Archive:  ../poc.zip
 extracting: boring_file             
$ cat boring_file
not python

输出boring_file的实现包括:

  • Go
  • java.util.zip(seek和streaming)
  • InfoZip(unzip)
  • MiniZip(zlib)
  • PHP
  • zip + async_zip Rust crates(seek和streaming)
  • Yauzl(npm)
  • net.lingala.zip4j(Maven)
  • libarchive(bsdunzip)

进一步分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# cpython/Lib/zipfile/__init__.py @ 6bf1c0ab3497b1b193812654bcdfd0c11b4192d8

# 简化实现,移除条件和错误处理

def _EndRecData64(fpin, offset, endrec):
    fpin.seek(offset - sizeEndCentDir64Locator, 2)
    data = fpin.read(sizeEndCentDir64Locator)

    sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data)

    # 假设没有'zip64可扩展数据'
    fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
    data = fpin.read(sizeEndCentDir64)
    # ...

上面的代码片段是当前用于读取zip64端部中央目录记录的逻辑。

sizeEndCentDir64LocatorsizeEndCentDir64都是从导入时的struct.calcsize派生的常量。

当读取zip64端部中央目录时,zip64定位器记录(reloff)被完全忽略,而是从记录大小常量计算偏移量。

注释"假设没有’zip64可扩展数据’“似乎表明这种"固定偏移"行为是有意的,因为读取"zip64可扩展数据"字段需要将zip64端部中央目录记录视为具有可变大小。

然而,通过做出这个假设,Python的zip实现现在与大多数其他实现不同,这些实现确实使用zip64定位器记录中的偏移量。

最后,没有验证无扩展数据的假设。没有检查reloff以确保它对应于实际读取的zip64端部中央目录记录的位置。这意味着reloff可以指向一个单独的zip64端部中央目录记录,该记录返回与Python读取的内容不同的内容。

时间线

  • 报告日期:2025年7月28日
  • 修复日期:未提供
  • 披露日期:2026年10月27日
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计