CVE-2026-40594: pyLoad: Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition)

Published Apr 16, 2026
·
Updated

Summary

The setsessioncookiesecure beforerequest 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 SESSIONCOOKIESECURE on every request. Because pyLoad uses the multi-threaded Cheroot WSGI server (requestqueuesize=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 SESSIONCOOKIESECURE according to the value of X-Forwarded-Proto TODO: Add trusted proxy check @app.beforerequest def setsessioncookiesecure(): xforwardedproto = flask.request.headers.get("X-Forwarded-Proto", "") issecure = ( xforwardedproto.split(',')[0].strip() == "https" or app.config["PYLOADAPI"].getconfigvalue("webui", "usessl") ) flask.currentapp.config['SESSIONCOOKIESECURE'] = issecure

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.currentapp.config['SESSIONCOOKIESECURE'] is application-wide shared state. When Thread A (attacker) writes False to this config, Thread B (victim) may read False when Flask's savesession() runs in the afterrequest phase, producing a Set-Cookie response without the Secure flag.

The Cheroot WSGI server is configured with requestqueuesize=512 in src/pyload/webui/webserverthread.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, usessl=False):

An attacker with direct access to the backend (e.g., in a containerized/Kubernetes deployment) sends concurrent requests to keep SESSIONCOOKIESECURE 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 SESSIONCOOKIESECURE=True on a plain HTTP server for i in $(seq 1 200); do curl -s -H 'X-Forwarded-Proto: https' http://localhost:8000/ & done

Concurrent legitimate users receive Set-Cookie with Secure flag Browser refuses to send Secure cookies over HTTP Users' sessions silently break — they appear logged out

The second attack path works against the default configuration (usessl=False) and requires no special network position.

Impact

- Session cookie exposure (Attack Path 1): When deployed behind a TLS-terminating proxy, an attacker can cause session cookies to be issued without the Secure flag. If the victim's browser subsequently makes an HTTP request (e.g., via a mixed-content link or downgrade attack), the session cookie is transmitted in cleartext, enabling session hijacking.

- Session denial of service (Attack Path 2): On default plain HTTP deployments, an attacker can continuously set SESSIONCOOKIESECURE=True, causing browsers to refuse sending session cookies back to the server. This silently breaks all concurrent users' sessions with no user-visible error message, only a redirect to login.

- No authentication required: Both attack paths are fully unauthenticated — the beforerequest handler fires before any auth checks.

Recommended Fix

Replace the global config mutation with per-response cookie handling, and add proxy validation:

python Option A: Set Secure flag per-response instead of mutating global config @app.afterrequest def setsessioncookiesecure(response): # Only trust X-Forwarded-Proto if ProxyFix is configured issecure = app.config["PYLOADAPI"].getconfigvalue("webui", "usessl") if 'Set-Cookie' in response.headers: # Modify cookie flags per-response, not global config cookies = response.headers.getlist('Set-Cookie') response.headers.remove('Set-Cookie') for cookie in cookies: if issecure and 'Secure' not in cookie: cookie += '; Secure' response.headers.add('Set-Cookie', cookie) return response

Option B (preferred): Use Werkzeug's ProxyFix with explicit trust from werkzeug.middleware.proxyfix import ProxyFix

In App.new, before returning: if trustedproxycount: # from config app.wsgiapp = ProxyFix(app.wsgiapp, xproto=trustedproxycount) Then set SESSIONCOOKIESECURE once at startup based on usessl config, and let ProxyFix handle X-Forwarded-Proto transparently

At minimum, remove the beforerequest handler entirely and set SESSIONCOOKIESECURE once at startup (line 130 already does this in configuresession). The dynamic per-request adjustment is the root cause of both the spoofing and the race condition.

Other sources

pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev98, the setsessioncookiesecure beforerequest 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 SESSIONCOOKIESECURE on every request. Because pyLoad uses the multi-threaded Cheroot WSGI server (requestqueuesize=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. This vulnerability is fixed in 0.5.0b3.dev98.

— MITRE

Affected Software

2 affected componentsFixes available
pip/pyload-ng<=0.5.0b3.dev97
0.5.0b3.dev98
Pyload-ng Project Pyload-ng Python<0.5.0b3.dev69

Event History

Apr 16, 2026
Advisory Published
via GitHub·01:20 AM
Data Sourced
via GitHub·01:20 AM
DescriptionSeverityWeaknessAffected Software
Apr 21, 2026
CVE Published
via MITRE·05:14 PM
Data Sourced
via MITRE·05:14 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·06:16 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-40594?

CVE-2026-40594 is considered a high-severity vulnerability due to its potential to expose sensitive session data.

2

How do I fix CVE-2026-40594?

To fix CVE-2026-40594, upgrade pyload-ng to version 0.5.0b3.dev98 or later.

3

What does CVE-2026-40594 affect?

CVE-2026-40594 affects the Flask configuration in pyload-ng versions up to 0.5.0b3.dev97.

4

What is exploited in CVE-2026-40594?

CVE-2026-40594 exploits the unvalidated reading of the X-Forwarded-Proto header from HTTP requests.

5

Can CVE-2026-40594 allow session hijacking?

Yes, CVE-2026-40594 can potentially allow session hijacking if the vulnerability is exploited by an untrusted proxy.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203