CVE-2026-33509
pyLoad allows users with SETTINGS permission to get RCE via reconnect.script.
- CVSS 8.8
- CWE-269
- Authentication, Authorization, and Session Management
- Remote
pyLoad is a free and open-source download manager written in Python. From version 0.4.0 to before version 0.5.0b3.dev97, the set_config_value() API endpoint allows users with the non-admin SETTINGS permission to modify any configuration option without restriction. The reconnect.script config option controls a file path that is passed directly to subprocess.run() in the thread manager's reconnect logic. A SETTINGS user can set this to any executable file on the system, achieving Remote Code Execution. The only validation in set_config_value() is a hardcoded check for general.storage_folder — all other security-critical settings including reconnect.script are writable without any allowlist or path restriction. This issue has been patched in version 0.5.0b3.dev97.
- CWE
- CWE-269
- CVSS base score
- 8.8
- Published
- 2026-03-24
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- pyLoad
- Fixed by upgrading
- Yes
Solution
Upgrade to pyLoad version 0.5.0b3.dev97 or later.
Vulnerable code sample
import subprocess
import sys
# This code is a simplified representation to demonstrate the vulnerability pattern
# described in CVE-2024-33509 (referred to as CVE-2026-33509 in the prompt).
# It simulates the vulnerable components of pyLoad before the fix.
class Config:
"""A mock configuration object to store application settings."""
def __init__(self):
self.settings = {
"general": {
"storage_folder": "/path/to/downloads"
},
"reconnect": {
"script": ""
}
}
def get_value(self, section, option):
return self.settings.get(section, {}).get(option)
def set_value(self, section, option, value):
if section in self.settings:
self.settings[section][option] = value
return True
return False
class VulnerableApp:
"""Represents the application with the vulnerable API and logic."""
def __init__(self):
self.config = Config()
def set_config_value(self, section, option, value):
"""
The vulnerable API endpoint as described in the CVE.
It allows a user with 'SETTINGS' permission to modify configuration.
"""
key = f"{section}.{option}"
# The only validation check is a hardcoded one for a specific setting,
# as described in the CVE advisory.
if key == "general.storage_folder":
# Simulate a basic validation for this specific setting.
if not isinstance(value, str) or not value.startswith("/"):
print(f"Validation failed for {key}", file=sys.stderr)
return False
# THE VULNERABILITY:
# No other checks are performed. Any other setting, including the
# security-critical 'reconnect.script', can be set to any value
# without restriction or validation.
return self.config.set_value(section, option, value)
def perform_reconnect(self):
"""
Simulates the reconnect logic in the thread manager that
insecurely uses the configured script path.
"""
script_path = self.config.get_value("reconnect", "script")
if script_path:
print(f"[+] Reconnect logic triggered. Executing script: {script_path}")
try:
# THE PAYLOAD EXECUTION:
# The user-controlled 'script_path' is passed directly to
# subprocess.run(), leading to Remote Code Execution.
# shell=True is used here to make the POC simple with command strings.
subprocess.run(script_path, shell=True, check=True)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
print(f"[-] Error executing reconnect script: {e}", file=sys.stderr)
else:
print("[+] Reconnect logic triggered. No script configured.")
# Example of how an attacker would exploit this.
# This part would be initiated by an attacker's API call.
if __name__ == '__main__':
# 1. Initialize the vulnerable application.
app = VulnerableApp()
# 2. Attacker's payload. This command will create a file to prove execution.
# On Linux/macOS:
malicious_command = "touch /tmp/pwned_by_cve"
# On Windows:
# malicious_command = "echo pwned > C:\\pwned_by_cve.txt"
print(f"[*] Attacker with 'SETTINGS' permission calls the API to set 'reconnect.script'.")
print(f"[*] Payload: {malicious_command}")
# 3. The attacker calls the vulnerable set_config_value function.
# The lack of an allowlist or proper validation allows this.
app.set_config_value("reconnect", "script", malicious_command)
print(f"[*] Config updated. Current 'reconnect.script': {app.config.get_value('reconnect', 'script')}")
# 4. Sometime later, the application's internal logic triggers a reconnect.
print("\n[*] Simulating application-triggered reconnect...")
app.perform_reconnect()
print("\n[*] Exploit finished. Check for the existence of '/tmp/pwned_by_cve' to verify RCE.")Patched code sample
import subprocess
# This example represents a simplified pyLoad configuration manager and its API.
# The core of the fix is to prevent non-admin users from modifying security-critical
# configuration settings.
class ConfigManager:
"""
A simplified representation of the configuration management system.
"""
def __init__(self, initial_config=None):
if initial_config is None:
self.config = {
"general": {"language": "en"},
"reconnect": {"script": ""}
}
else:
self.config = initial_config
# --- Start of the fix for CVE-2026-33509 ---
# A set of security-critical options that should only be modified by an admin.
# This prevents users with only 'SETTINGS' permission from changing them.
SENSITIVE_OPTIONS = {
"reconnect.script"
# Other security-critical options would be added here.
}
def set_config_value(self, section, option, value, user_permissions):
"""
Sets a configuration value after performing security checks.
Args:
section (str): The configuration section (e.g., 'reconnect').
option (str): The configuration option (e.g., 'script').
value (str): The new value for the option.
user_permissions (set): A set of permissions for the user (e.g., {'SETTINGS', 'ADMIN'}).
Raises:
PermissionError: If the user lacks the required permissions.
ValueError: If the configuration option does not exist.
"""
if "SETTINGS" not in user_permissions:
raise PermissionError("User does not have 'SETTINGS' permission.")
full_option_key = f"{section}.{option}"
# THE FIX: Check if the option is in the sensitive list and if the user is NOT an admin.
# Before the patch, this check did not exist, allowing a non-admin 'SETTINGS'
# user to modify 'reconnect.script'.
if full_option_key in self.SENSITIVE_OPTIONS and "ADMIN" not in user_permissions:
raise PermissionError(f"Admin privileges are required to modify the sensitive option '{full_option_key}'.")
# --- End of the fix for CVE-2026-33509 ---
if section in self.config and option in self.config[section]:
print(f"User with permissions {user_permissions} successfully set '{full_option_key}' to '{value}'.")
self.config[section][option] = value
else:
raise ValueError(f"Configuration option '{full_option_key}' not found.")
def run_reconnect_script(config_manager):
"""
Simulates the part of the application that uses the 'reconnect.script' config.
"""
script_path = config_manager.config.get("reconnect", {}).get("script", "")
if script_path:
print(f"\nAttempting to execute reconnect script: {script_path}")
# In a real scenario, this could be a malicious command.
# For safety, this example will not use subprocess.run directly on the path.
# Instead, we will just print what would have happened.
print(f"SIMULATION: subprocess.run(['{script_path}']) would be called here.")
# Example of the dangerous call that would have been made:
# subprocess.run([script_path], shell=True)
else:
print("\nNo reconnect script is configured.")
if __name__ == '__main__':
# DEMONSTRATION OF THE FIX
# 1. Setup
config_manager = ConfigManager()
admin_user_permissions = {"SETTINGS", "ADMIN"}
settings_only_user_permissions = {"SETTINGS"}
print("--- Demonstrating the patched ConfigManager ---")
print(f"Initial config: {config_manager.config}")
# 2. Malicious attempt by a non-admin user (with only 'SETTINGS' permission)
print("\n[ATTEMPT 1] A non-admin user tries to set 'reconnect.script' to a malicious command.")
try:
# This user only has 'SETTINGS', not 'ADMIN'.
config_manager.set_config_value(
section="reconnect",
option="script",
value="/usr/bin/touch /tmp/pwned",
user_permissions=settings_only_user_permissions
)
except PermissionError as e:
print(f"SUCCESSFULLY BLOCKED: {e}")
print(f"Config after failed attempt: {config_manager.config}")
run_reconnect_script(config_manager) # Script is not set, RCE is prevented.
# 3. Legitimate attempt by a non-admin user to change a non-sensitive setting
print("\n[ATTEMPT 2] A non-admin user tries to set a non-sensitive option 'general.language'.")
try:
config_manager.set_config_value(
section="general",
option="language",
value="de",
user_permissions=settings_only_user_permissions
)
print("SUCCESS: Non-sensitive setting changed.")
except PermissionError as e:
print(f"FAILED (UNEXPECTED): {e}")
print(f"Config after successful change: {config_manager.config}")
# 4. Legitimate attempt by an admin user to change the sensitive setting
print("\n[ATTEMPT 3] An admin user legitimately sets the 'reconnect.script'.")
try:
config_manager.set_config_value(
section="reconnect",
option="script",
value="/usr/local/bin/my_reconnect.sh",
user_permissions=admin_user_permissions
)
except PermissionError as e:
print(f"FAILED (UNEXPECTED): {e}")
print(f"Config after admin change: {config_manager.config}")
run_reconnect_script(config_manager) # Script is now set by an authorized user.Payload
{
"section": "reconnect",
"option": "script",
"value": "touch /tmp/pwned"
}
Cite this entry
@misc{vaitp:cve202633509,
title = {{pyLoad allows users with SETTINGS permission to get RCE via reconnect.script.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-33509},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-33509/}}
}
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 ::
