Arbitrary file writes through archive extraction (Tar Slip)

Arbitrary file writes through archive extraction (Tar Slip)

Description

Tar Slip uses crafted paths in a tar archive to write files outside the intended extraction directory. Absolute paths and relative paths such as ../ can allow important system files or sensitive data to be overwritten.

Potential impact

  • File-system damage: An attacker may overwrite system files and disrupt the system.
  • Information disclosure: Sensitive information may be exposed to an attacker.
  • Privilege escalation: Modifying security-sensitive system files may allow an attacker to gain additional privileges.

Remediation

  • Use the standard extraction filter: on supported Python versions, specify extractall(..., filter="data") to restrict absolute paths, links outside the destination, special files, and excessive permissions.
  • Use a safe destination: extract into a new private directory under a trusted parent.
  • Protect legacy runtimes: without extraction filters, check the resolved path against the directory boundary and reject symbolic links, hard links, device files, and other non-regular entries.
  • Limit resources: extraction filters alone do not prevent archive bombs or excessive file counts. Limit total size, number of files, and processing time separately.

Examples

Before

python
# Tar extraction using the runtime default filter
import tarfile

def unsafe_extract(tar_file_path, extract_path):
    with tarfile.open(tar_file_path) as tar:
        tar.extractall(path=extract_path)

After

python
# Safe tar extraction
import tarfile
import os
import shutil
from pathlib import Path

def safe_extract(tar_file_path, extract_path):
    destination = Path(extract_path)
    # Extract only into a new private directory without existing files or links
    destination.mkdir(mode=0o700, exist_ok=False)
    destination = destination.resolve()

    with tarfile.open(tar_file_path) as tar:
        if hasattr(tarfile, "data_filter"):
            # Restricted data extraction on runtimes that support extraction filters
            tar.extractall(path=destination, filter="data")
            return

        # Legacy fallback: reject links and special files, and check directory boundaries
        for member in tar.getmembers():
            if member.issym() or member.islnk():
                raise ValueError("Tar links are not allowed")
            if not (member.isfile() or member.isdir()):
                raise ValueError("Unsupported tar entry type")

            target = (destination / member.name).resolve()
            try:
                target.relative_to(destination)
            except ValueError as error:
                raise ValueError("Tar entry escapes extraction directory") from error

            if member.isdir():
                target.mkdir(parents=True, exist_ok=True, mode=0o700)
                continue

            target.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
            source = tar.extractfile(member)
            if source is None:
                raise ValueError("Tar entry has no file data")
            fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
            with source, os.fdopen(fd, "wb") as output:
                shutil.copyfileobj(source, output)

Explanation:

  • Before: No extraction filter is specified. Python 3.14 uses data by default, so this call alone does not mean paths outside the destination are allowed. Earlier versions or environments with a different default filter may permit writes through absolute paths or ../.
  • After: The function creates a new private directory and explicitly uses the standard data filter where available. The legacy fallback checks each resolved path and rejects symbolic links, hard links, and special files, addressing link-based escapes that a simple .. name check would miss.

References