GitHub Security Advisory: GHSA-ppvx-rwh9-7rj7
Package: pyload-ng (PIP)
Severity: MODERATE (CVSS 6.8)
CWE: CWE-863: Incorrect Authorization
Vulnerable versions: < 0.5.0b3.dev97
Patched in: 0.5.0b3.dev97

Summary: pyload-ng: Authorization Bypass for SSL Certificate/Key Configuration Due to Option Name Mismatch in pyload-ng

Details:
## Summary

The `ADMIN_ONLY_CORE_OPTIONS` authorization set in `set_config_value()` uses incorrect option names `ssl_cert` and `ssl_key`, while the actual configuration option names are `ssl_certfile` and `ssl_keyfile`. This name mismatch causes the admin-only check to always evaluate to False, allowing any user with SETTINGS permission to overwrite the SSL certificate and key file paths. Additionally, the `ssl_certchain` option was never added to the admin-only set at all.

## Details

The vulnerability is in `src/pyload/core/api/__init__.py`. The `ADMIN_ONLY_CORE_OPTIONS` set is defined at lines 237-248:

```python
ADMIN_ONLY_CORE_OPTIONS = {
    ("general", "storage_folder"),
    ("log", "syslog_host"),
    ("log", "syslog_port"),
    ("proxy", "password"),
    ("proxy", "username"),
    ("reconnect", "script"),
    ("webui", "host"),
    ("webui", "ssl_cert"),      # BUG: should be "ssl_certfile"
    ("webui", "ssl_key"),       # BUG: should be "ssl_keyfile"
    ("webui", "use_ssl"),
}
# NOTE: ("webui", "ssl_certchain") is entirely missing
```

The actual config option names are defined in `src/pyload/core/config/default.cfg:39-41`:

```
file ssl_certfile : "SSL Certificate" = ssl.crt
file ssl_keyfile : "SSL Key" = ssl.key
file ssl_certchain : "CA's intermediate certificate bundle (optional)" =
```

The authorization check at line 267 compares the incoming `(category, option)` tuple against this set:

```python
if (category, option) in ADMIN_ONLY_CORE_OPTIONS and not is_admin:
    self.pyload.log.error(...)
    return
```

When a request arrives with `option=ssl_certfile`, the check evaluates `("webui", "ssl_certfile") in ADMIN_ONLY_CORE_OPTIONS` which is **False** because the set contains `("webui", "ssl_cert")`, not `("webui", "ssl_certfile")`. The admin-only guard is bypassed and `config.set()` at line 271 proceeds to write the attacker-supplied value.

The value is cast as a `file` type in `parser.py:300-305`, which resolves it via `os.path.realpath()` but performs no further validation:

```python
elif typ in ("file", "folder"):
    return (
        ""
        if value in (None, "")
        else os.path.realpath(os.path.expanduser(os.fsdecode(value)))
    )
```

On server restart with SSL enabled, the webserver loads the attacker-controlled paths (`webserver_thread.py:22-23,51-52`):

```python
self.certfile = self.pyload.config.get("webui", "ssl_certfile")
self.keyfile = self.pyload.config.get("webui", "ssl_keyfile")
# ...
self.server.ssl_adapter = BuiltinSSLAdapter(
    self.certfile, self.keyfile, self.certchain
)
```

## PoC

Prerequisites: A pyLoad instance with SSL enabled and a non-admin user account that has SETTINGS permission.

**Step 1:** Authenticate as the non-admin user to get a session cookie:
```bash
curl -c cookies.txt -X POST 'http://localhost:8000/login' \
  -d 'username=settingsuser&[CREDENTIAL_REDACTED]'
```

**Step 2:** Set the SSL certificate to an attacker-controlled file path:
```bash
curl -b cookies.txt -X POST 'h