GitHub Security Advisory: GHSA-mp82-fmj6-f22v
Package: pyload-ng (PIP)
Severity: MODERATE (CVSS 4.8)
CWE: CWE-346: Origin Validation Error
Vulnerable versions: <= 0.5.0b3.dev97
Patched in: 0.5.0b3.dev98

Summary: pyLoad has a Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition)

Details:
## Summary

The `set_session_cookie_secure` `before_request` handler in `src/pyload/webui/app/__init__.py` reads the `X-Forwarded-Proto` header from any HTTP request without validating that the request originates from a trusted proxy, then mutates the **global** Flask configuration `SESSION_COOKIE_SECURE` on every request. Because pyLoad uses the multi-threaded Cheroot WSGI server (`request_queue_size=512`), this creates a race condition where an attacker's request can influence the `Secure` flag on other users' session cookies — either downgrading cookie security behind a TLS proxy or causing a session denial-of-service on plain HTTP deployments.

## Details

The vulnerable code is in `src/pyload/webui/app/__init__.py:75-84`:

```python
# Dynamically set SESSION_COOKIE_SECURE according to the value of X-Forwarded-Proto
# TODO: Add trusted proxy check
@app.before_request
def set_session_cookie_secure():
    x_forwarded_proto = flask.request.headers.get("X-Forwarded-Proto", "")
    is_secure = (
        x_forwarded_proto.split(',')[0].strip() == "https" or
        app.config["PYLOAD_API"].get_config_value("webui", "use_ssl")
    )
    flask.current_app.config['SESSION_COOKIE_SECURE'] = is_secure
```

The root cause has two components:

1. **No origin validation (CWE-346):** The `X-Forwarded-Proto` header is read from any client request. This header is only trustworthy when set by a known reverse proxy. Without `ProxyFix` middleware or a trusted proxy allowlist, any client can spoof it. The code itself acknowledges this with the TODO on line 76.

2. **Global state mutation in a multi-threaded server:** `flask.current_app.config['SESSION_COOKIE_SECURE']` is application-wide shared state. When Thread A (attacker) writes `False` to this config, Thread B (victim) may read `False` when Flask's `save_session()` runs in the after_request phase, producing a `Set-Cookie` response without the `Secure` flag.

The Cheroot WSGI server is configured with `request_queue_size=512` in `src/pyload/webui/webserver_thread.py:46`, confirming concurrent multi-threaded request processing.

No `ProxyFix` or equivalent middleware is configured anywhere in the codebase (confirmed via codebase-wide search).

## PoC

**Attack Path 1 — Cookie Security Downgrade (behind TLS-terminating proxy, `use_ssl=False`):**

An attacker with direct access to the backend (e.g., in a containerized/Kubernetes deployment) sends concurrent requests to keep `SESSION_COOKIE_SECURE` set to `False`:

```bash
# Attacker floods backend directly, bypassing TLS proxy
for i in $(seq 1 200); do
  curl -s -H 'X-Forwarded-Proto: http' http://pyload-backend:8000/ &
done

# Meanwhile, a legitimate user behind the TLS proxy receives a session cookie
# During the race window, their Set-Cookie header lacks the Secure flag
# The cookie is then vulnerable to interception over plain HTTP
```

**Attack Path 2 — Session Denial of Service (default plain HTTP deployment):**

```bash
# Attacker causes SESSION_COOKIE_SE