Oct 9, 2026·6 min read·5 visits
Unconditional trust of X-Forwarded-For headers in pyLoad allows remote attackers to bypass login rate limits and forge source IP addresses in audit logs.
A critical security vulnerability has been identified and patched in the WebUI component of pyLoad, a popular Python-based open-source download manager. This vulnerability allows attackers to completely bypass API rate limits and spoof client IP addresses in audit logs due to unsafe parsing of the X-Forwarded-For HTTP header.
The webui component of pyLoad exposes an attack surface through HTTP endpoints that require authentication, rate limiting, and origin verification. The application is designed to act as an open-source download manager, frequently deployed on localized servers, network-attached storage (NAS) devices, or within containerized environments. In default configurations, pyLoad may run directly on standard WSGI servers or behind unconfigured reverse proxies.\n\nThe flaw lies in the incorrect handling of client-supplied network headers within the backend server logic. Specifically, the application parses the X-Forwarded-For HTTP header without verifying if the request originated from a trusted upstream reverse proxy. This behavior falls under CWE-348, where an application utilizes a less trusted source to determine IP addresses. This allows remote attackers to spoof their client IP address and bypass security controls.\n\nThe primary impacts of this vulnerability include the complete evasion of API rate limits and the manipulation of audit logs. Rate limiting is applied to critical endpoints such as the login panel to prevent automated credential guessing. By forging the source IP, an attacker can allocate a fresh rate-limiting bucket for every authentication request. Furthermore, the logging mechanism registers the spoofed IP address, which hampers security monitoring and post-incident forensic investigations.
The root cause of GHSA-9Q47-3CM2-2RP8 is the unconditional trust placed in the X-Forwarded-For (XFF) HTTP header. The XFF header is a standard mechanism used by reverse proxies to pass the original client IP address to backend servers. However, since HTTP headers can be easily manipulated by any client, a backend application must only trust this header if it is explicitly configured to do so and if the request arrives from a known, trusted proxy server.\n\nIn pyLoad, the custom IP extraction logic was implemented across multiple blueprints and helpers. The code extracted the first IP address from the comma-separated list in the X-Forwarded-For header. If the header was present, its value was immediately used as the authoritative client IP. If the header was absent, the code fell back to the socket's peer address, represented by flask.request.remote_addr.\n\nBecause the application lacked any mechanism to verify the identity of the proxy sending the request, direct client connections could include an arbitrary X-Forwarded-For header. The application would process this header, split the values, and accept the leftmost IP address. This design flaw violates the trust boundary between the external untrusted network and the internal application security context.
The vulnerability was present in several locations where the client IP was determined. In the administrative login logic (app_blueprint.py) and the rate-limiting decorator (helpers.py), the IP address was extracted using manual string parsing:\n\npython\n# Vulnerable manual extraction\nclient_ip = flask.request.headers.get("X-Forwarded-For", "").split(',')[0].strip() or flask.request.remote_addr\n\n\nThe remediation replaced this manual parsing with a structured helper function and Werkzeug's native middleware. The patch introduced a trusted_proxies configuration parameter, defaulting to 0. When this parameter is set to a value greater than 0, the application wraps its WSGI layers using Werkzeug's ProxyFix middleware:\n\npython\n# Patched middleware initialization\nif trusted_proxy_count:\n app.wsgi_app = ProxyFix(app.wsgi_app, x_for=trusted_proxy_count)\n\n\nThe centralized helper function get_client_ip was introduced to enforce this boundary. If ignore_proxyfix is set to True, the function retrieves the original socket address, ensuring that security-critical loopback validations cannot be bypassed via headers:\n\npython\n# Patched helper in helpers.py\ndef get_client_ip(request=flask.request, ignore_proxyfix=False):\n remote_addr = request.remote_addr or "unknown"\n if ignore_proxyfix:\n proxy_fix_original = request.environ.get("werkzeug.proxy_fix.orig", {})\n return proxy_fix_original.get("REMOTE_ADDR", remote_addr)\n return remote_addr\n
Exploitation of this vulnerability requires no special privileges and can be performed remotely. To bypass rate limits on the login panel, an attacker generates a sequence of HTTP POST requests targeting the /api/login endpoint. Each request is constructed with a unique IP address placed in the X-Forwarded-For header.\n\npython\n# Conceptual rate-limit evasion script\nimport requests\nimport random\n\ntarget_url = "http://localhost:8000/api/login"\nfor attempt in range(10):\n fake_ip = f"192.0.2.{random.randint(1, 254)}"\n headers = {"X-Forwarded-For": fake_ip}\n payload = {"username": "admin", "password": f"attempt_{attempt}"}\n response = requests.post(target_url, headers=headers, data=payload)\n\n\nThe server processes each request as if it originated from a distinct client. Consequently, the sliding-window counter used by the @rate_limit decorator is reset for each iteration, allowing an infinite number of login attempts.\n\nAn attacker can also inject a static IP address to frame an arbitrary system in the server logs. For example, sending a request with X-Forwarded-For: 8.8.8.8 forces pyLoad to write "8.8.8.8" as the source of the authentication failure in the security log. This technique obscures the actual location of the attacker during active scanning or brute-force operations.
The primary security impact is the negation of brute-force protection mechanisms. Rate limiting is a crucial control used to throttle high-frequency automated attacks against authentication gateways. By rendering this control ineffective, the vulnerability permits attackers to perform high-velocity dictionary attacks against administrative accounts.\n\nThe secondary impact is the degradation of audit trail integrity. Security teams rely on application logs to trace malicious actions and perform incident response. By allowing arbitrary IP injection, an attacker can misdirect investigators, evade geolocation-based detection, and complicate log correlation efforts.\n\nThe vulnerability also poses a risk of access control bypass on localized interfaces such as Click'N'Load. This component restricts interactions to local loopback addresses. If the proxy-fix middleware is applied globally without safety checks, an external attacker could spoof a loopback IP (127.0.0.1) and access administrative loopback endpoints.
The primary remediation is to upgrade pyLoad to a version containing the commit 5dc6b628e2d8b9f42dde82f396e5b7a2b513f6d1. This update ensures that the application no longer parses the X-Forwarded-For header by default.\n\nAdministrators must configure the trusted_proxies option in the webui configuration file. If pyLoad is deployed directly to the internet, this value must remain at 0. This forces the application to utilize only the socket's peer address, ignoring any incoming X-Forwarded-For headers.\n\nIf the application is deployed behind a reverse proxy, the trusted_proxies value must be set to match the exact number of proxy hops. For example, a standard deployment behind a single Nginx reverse proxy requires a configuration value of 1. Over-provisioning this value will recreate the vulnerability by trusting client-forged headers from the untrusted network.
| Attribute | Detail |
|---|---|
| CWE ID | CWE-348 |
| Attack Vector | Network |
| CVSS | 6.5 |
| Impact | Rate-Limit Bypass & Log Spoofing |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
An insufficient session invalidation vulnerability exists in pyLoad (pyload-ng) versions 0.5.0b3.dev98 through 0.5.0b3.dev101. When administrative actions like privilege revocation or password changes are executed via the public REST API, active user sessions on disk are not updated or invalidated. Consequently, affected sessions remain fully authenticated with stale permissions for up to 31 days.
An authorization bypass and credential oracle vulnerability in pyload-ng allows authenticated users with minimal or no privileges to brute-force the administrator password. This is achieved through sensitive API methods exposed globally combined with a non-constant-time password hash comparison algorithm.
An authentication bypass vulnerability in pyLoad allows unauthenticated remote attackers to gain administrative API access. The vulnerability is caused by a logical flaw in the API key cache validation lookup, where authentication states are cached using only the public key identifier, skipping cryptographic token verification on cache hits.
An authorization bypass vulnerability in Strawberry GraphQL between versions 0.217.0 and 0.326.1 occurs when a synchronous permission handler returns an awaitable object (such as an unawaited coroutine). Due to Python's truthiness rules, the unawaited coroutine is evaluated as True, leading to an immediate bypass of security policies.
An uncontrolled resource consumption vulnerability in Strawberry GraphQL allows unauthenticated remote attackers to trigger a Denial of Service on persistent WebSocket connections using the legacy graphql-ws protocol. When the server enforces max_subscriptions_per_connection, naturally terminating subscriptions are not cleared from memory registries, leading to exhaustion of connection slots.
A high-severity type-confusion vulnerability exists in NearForm fast-jwt prior to version 6.3.0. The vulnerability allows attackers to bypass crucial claim validation steps (such as expiration, issuer, audience, and subject validations) by presenting a validly signed JSON Web Token structured as a JSON array instead of a JSON object. This occurs because the library's decoder fails to reject JSON arrays during type evaluation.