pyLoad-ng, an open-source download manager, ships with a critical flaw in its web UI that lets attackers tamper with session cookie security. The issue stems from blindly trusting the X-Forwarded-Proto header to flip the global SESSION_COOKIE_SECURE flag in Flask. Running on a multi-threaded Cheroot server with a 512-request queue, this triggers race conditions. Attackers can downgrade cookies to insecure mode behind TLS proxies or force Secure flags on plain HTTP setups, killing sessions. Fix it now if you’re running this.
Vulnerability Breakdown
The problem sits in src/pyload/webui/app/__init__.py, lines 75-84. A before_request handler grabs X-Forwarded-Proto from any incoming request—no proxy validation. It then overwrites the app-wide SESSION_COOKIE_SECURE config based on that header or a local SSL setting. The code even flags it with a TODO: “Add trusted proxy check.” Developers knew, but left it hanging.
pyLoad uses Cheroot WSGI server, threaded with request_queue_size=512 as set in src/pyload/webui/webserver_thread.py:46. Multiple threads hit the shared global config simultaneously. Attacker thread sets it to False (spoofing “http”), victim’s thread reads it during session save, and boom—their Set-Cookie lacks the Secure flag.
# 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
No ProxyFix middleware or trusted proxy list anywhere in the codebase. A full search confirms it. This violates CWE-346: Origin Validation Error. Clients spoof headers freely.
Attack Scenarios
Scenario 1: TLS proxy setup (e.g., containerized or Kubernetes, common for pyLoad). Backend listens on HTTP, proxy handles TLS. Set use_ssl=False. Attacker hits backend directly—maybe via pod networking or misconfig—floods with spoofed “http” headers.
# Attacker floods backend, bypassing proxy
for i in $(seq 1 200); do
curl -s -H 'X-Forwarded-Proto: http' http://pyload-backend:8000/ &
done
Legit user requests roll in via proxy (sends “https”). Race hits: victim’s cookie sets without Secure. Browser sends it over HTTP next time, ripe for interception. In shared hosting or clusters, one bad actor ruins it for all.
Scenario 2: Plain HTTP deployment (pyLoad default). Attacker spoofs “https”, forcing Secure=True globally. Victim’s cookies get Secure flag on HTTP responses. Browsers reject them outright—no sessions, login fails. Pure denial-of-service.
# DoS via forced Secure on HTTP
for i in $(seq 1 200); do
curl -s -H 'X-Forwarded-Proto: https' http://pyload:8000/ &
done
Flood sustains the flag. pyLoad’s web UI relies on sessions for auth—users locked out until restart or attacker tires.
Why It Matters and How to Fix
pyLoad-ng targets users managing large downloads via web—torrents, premium hosters. Sessions hold auth tokens, download queues. Insecure cookies leak creds to network snoops. DoS halts operations. Exposed installs (Shodan shows thousands) amplify risk; many run in Docker without isolation.
Flask globals in threaded apps scream trouble—thread-safety nightmare. Broader lesson: dynamic config flips on untrusted input never work without locks or per-request isolation. pyLoad ignores Flask best practices like app contexts properly.
Fixes: Patch adds trusted_proxies list, validate sender IP. Or ditch dynamic toggle—hardcode based on deploy config. Use ProxyFix from Werkzeug. Upstream issue: track at pyload/pyload-ng GitHub. Versions affected: latest as of advisory (check commits post-2023).
Verify your setup. Scan for pyLoad on 8000/tcp. If behind proxy, test PoC. Restarting clears state temporarily, but races persist. In Kubernetes, NetworkPolicies block direct backend access. Skeptical take: open-source tools like this prioritize features over hardening. Users pay the price—audit your stack.
Word count: 612