FlawOpen

flawopen.com/Path Traversal/Zip Slip

What is a Zip Slip vulnerability?

Reference page
ELI5

A zip file's internal file list can include entries named things like ../../etc/cron.d/evil. If the code extracting the archive trusts those names blindly, "unzipping a file" can secretly write files anywhere on disk the process has permission to reach — the archive extraction itself is a path traversal vector.

Why it's easy to miss

Archive extraction feels like a solved, boring operation — most developers don't think of "unzip this file" as a place untrusted input reaches the filesystem. But every entry name inside a zip (or tar) archive is attacker-controlled if the archive itself came from an untrusted source (a user upload, a downloaded dependency), and naive extraction code that does outputDir + entry.name without validation is exactly the path-traversal pattern, just triggered by archive contents instead of a URL parameter.

VULNERABLE (conceptual)
for entry in zip_file:
    dest = os.path.join(output_dir, entry.name)
    write_file(dest, entry.read())
# entry.name could be "../../../etc/cron.d/x"
LEXICAL CONTAINMENT CHECK — not race-safe alone
root = os.path.realpath(output_dir)
os.makedirs(root, exist_ok=True)
for entry in zip_file:
    dest = os.path.realpath(
      os.path.join(root, entry.name)
    )
    try:
        inside_root = os.path.commonpath([root, dest]) == root
    except ValueError:
        inside_root = False
    if not inside_root:
        raise ValueError("Unsafe entry path")
    write_file(dest, entry.read())

This containment check prevents lexical path escapes, including sibling names that share a string prefix. Extract into a newly created, private directory; reject symlink entries and account for pre-existing symlinks or concurrent filesystem changes, which require race-safe filesystem APIs or a maintained extraction library.

FAQ

Python archive APIs have different path protections

The code pair above is a conceptual custom extraction loop; it is not Python’s built-in ZipFile.extractall(). Python documents that ZipFile.extract() and extractall() attempt to prevent absolute-path and .. escapes, but still warns you to inspect untrusted archives. zipfile.Path does not sanitize member names, so callers must validate them. For TAR archives, extraction filters arrived in Python 3.12; Python 3.14 changed the default to filter="data". Set that filter explicitly on supported runtimes where the default may be less restrictive. Even the data filter does not block every unsafe behavior or resource-exhaustion risk. Extract in a fresh private staging directory and enforce limits on archive size, member count, total expanded bytes, and processing time.

Do modern archive libraries handle this automatically?

Some newer library versions added protections after this pattern was widely publicized in 2018, but many still don't validate by default — check your specific library's documentation rather than assuming, and validate explicitly regardless.

References

See also Path Traversal (CWE-22): How It Works and How to Prevent It in Every Language — flawopen.com Node.js path.join() Does Not Prevent Traversal PHP include() and User-Controlled Paths Python os.path.join() Path Traversal Fix