Vulnerabilities in pkg:pypi/pyload-ng

PACKAGE
Advisory Summary

GHSA-68w4-83fh-f2w8 is a HIGH (CVSS 8.1) remote code execution vulnerability (CWE-287: Improper Authentication) affecting pyload-ng libraries in the PyPI ecosystem. The issue involves getuserdata/get_userdata exposed at perms.any allow any authenticated account to brute-force the administrator password. Remediation is available in the latest vendor maintenance releases.

59 Known Advisory Record(s)
Package Vulnerability Intelligence

pkg:pypi/pyload-ng

59 Known Advisory Record(s)

Vulnerability advisories affecting package pkg:pypi/pyload-ng. Select an advisory below to view technical details or fixes.

Advisory ID CVE / NVD Severity Summary
GHSA-68w4-83fh-f2w8 - HIGH pyload-ng: getUserData/get_userdata exposed at Perms.ANY allow any authenticated account to brute-force the administrator password
GHSA-9q47-3cm2-2rp8 - MODERATE pyLoad: Rate-Limit Bypass and Audit-Log Spoofing via Trusted Client-Controlled `X-Forwarded-For` Header
GHSA-jq7h-wrvp-3rgx - HIGH pyLoad: Privilege revocation and password change through the REST API do not invalidate the user's session
GHSA-889w-m37p-88m5 - HIGH pyLoad: Api.set_user_permission never invalidates the target's session
GHSA-p3pr-8f3m-4qp8 - MODERATE pyLoad WindowsPhoneNotify addon: non-admin SETTINGS user triggers SSRF via unguarded http.client notification host
GHSA-fr26-jjhm-638c - HIGH pyLoad: Tar extraction creates device nodes and FIFOs (member types not filtered; tarfile extractall without filter=)
GHSA-r44w-v6gf-x3p6 - HIGH pyLoad has an authentication bypass in API key validation (check_apikey cache)
GHSA-j92p-c242-7hfx CVE-2026-75597 MODERATE pyLoad: Unauthenticated access to /web/<path:filename> bypasses authentication on sensitive templates and leaks internal error details via exception attribute typo
GHSA-vq8p-m3wm-gv5f CVE-2026-48484 MODERATE pyLoad: Lack of Input Size Validation Leads to Denial of Service (DoS) and Process Termination
GHSA-c2f9-4mc8-j656 CVE-2026-48987 MODERATE pyLoad: Unbounded Memory Growth Leading to DoS and Potential DDoS in EventManager
GHSA-m5x5-28jr-gpjj CVE-2026-48737 MODERATE pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
GHSA-8rp3-xc6w-5qp5 CVE-2026-46561 MODERATE pyload-ng: SSRF via HTTP Redirect Bypass in parse_urls API
GHSA-fcjq-435v-jx94 CVE-2026-45348 HIGH pyLoad is vulnerable to stored XSS in Downloads view via unsanitized link URL in packages.js template literal
GHSA-w727-595x-pc3r CVE-2026-45306 MODERATE pyLoad Has Incomplete Fix for CVE-2026-33509 -storage_folder Bypass via Session Directory in pyLoad
GHSA-838g-gr43-qqg9 CVE-2026-42315 HIGH PyLoad vulnerable to Path Traversal via Package Folder Name in set_package_data
GHSA-97r3-5w84-r4q8 CVE-2026-42314 MODERATE PyLoad Vulnerable to Path Traversal via Package Folder Name
GHSA-pg67-9wjv-mr85 CVE-2026-42313 HIGH pyload-ng: non-admin SETTINGS users can redirect all outbound traffic through an attacker-controlled proxy via unrestricted `proxy.*` config (incomplete fix for CVE-2026-33509 / -35463 / -35464 / -35586)
GHSA-ccxc-x975-4hh9 CVE-2026-42312 MODERATE pyload-ng: non-admin SETTINGS users can disable outbound TLS peer verification via unrestricted `ssl_verify` config (incomplete fix for CVE-2026-33509 / -35463 / -35464 / -35586)
GHSA-mp82-fmj6-f22v CVE-2026-40594 MODERATE pyLoad has a Session Cookie Security Downgrade via Untrusted X-Forwarded-Proto Header Spoofing (Global State Race Condition)
GHSA-mvwx-582f-56r7 CVE-2026-35592 MODERATE pyload-ng: Incomplete Tar Path Traversal Fix in UnTar._safe_extractall via os.path.commonprefix Bypass
GHSA-ppvx-rwh9-7rj7 CVE-2026-35586 MODERATE pyload-ng: Authorization Bypass for SSL Certificate/Key Configuration Due to Option Name Mismatch in pyload-ng
GHSA-q485-cg9q-xq2r CVE-2026-33314 MODERATE Improper Authentication and Origin Validation Error in pyload-ng
GHSA-6px9-j4qr-xfjw CVE-2026-29778 HIGH pyLoad has an Arbitrary File Write via Path Traversal in edit_package()
GHSA-w7hq-f2pj-c53g CVE-2024-47821 HIGH pyLoad vulnerable to remote code execution by download to /.pyload/scripts using /flashgot API
GHSA-c3gc-9pf2-84gg CVE-2026-44226 MODERATE PyLoad vulnerable to unauthenticated traceback disclosure via global exception handler in WebUI
GHSA-cjjf-27cc-pvmv CVE-2025-61773 HIGH pyLoad CNL and captcha handlers allow Code Injection via unsanitized parameters
GHSA-66hx-chf7-3332 CVE-2026-41133 HIGH pyLoad has Stale Session Privilege After Role/Permission Change (Privilege Revocation Bypass)
GHSA-fj52-5g4h-gmq8 - LOW pyLoad's Session Not Invalidated After Permission Changes
GHSA-rfgh-63mg-8pwm CVE-2026-40071 MODERATE pyload-ng has a WebUI JSON permission mismatch that lets ADD/DELETE users invoke MODIFY-only actions
GHSA-4744-96p5-mp2j CVE-2026-35464 HIGH pyLoad: Unprotected storage_folder enables arbitrary file write to Flask session store and code execution (Incomplete fix for CVE-2026-33509)
GHSA-w48f-wwwf-f5fr CVE-2026-35463 HIGH pyLoad: Improper Neutralization of Special Elements used in an OS Command
GHSA-7gvf-3w72-p2pg CVE-2026-35459 CRITICAL pyLoad: SSRF filter bypass via HTTP redirect in BaseDownloader (Incomplete fix for CVE-2026-33992)
GHSA-2wvg-62qm-gj33 CVE-2026-35187 HIGH pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter
GHSA-m74m-f7cr-432x CVE-2026-33992 CRITICAL pyLoad: Server-Side Request Forgery via Download Link Submission Enables Cloud Metadata Exfiltration
GHSA-r7mc-x6x7-cqxx CVE-2026-33509 HIGH pyLoad SETTINGS Permission Users Can Achieve Remote Code Execution via Unrestricted Reconnect Script Configuration
GHSA-9gjj-6gj7-c4wj CVE-2025-57751 HIGH Denial-of-Service attack in pyLoad CNL Blueprint using dukpy.evaljs
GHSA-pwh4-6r3m-j2rf CVE-2025-55156 HIGH PyLoad vulnerable to SQL Injection via API /json/add_package in add_links parameter
GHSA-48rp-jc79-2264 CVE-2025-54802 CRITICAL pyLoad CNL Blueprint allows Path Traversal through `dlc_path` which leads to Remote Code Execution (RCE)
GHSA-3wwm-hjv7-23r3 - MODERATE Pyload log Injection via API /json/add_package in add_name parameter
GHSA-xqpg-92fq-grfg CVE-2025-54140 HIGH `pyLoad` has Path Traversal Vulnerability in `json/upload` Endpoint that allows Arbitrary File Write
GHSA-8w3f-4r8f-pf53 CVE-2025-53890 CRITICAL pyLoad vulnerable to XSS through insecure CAPTCHA
GHSA-x698-5hjm-w2m5 CVE-2025-7346 HIGH pyLoad is vulnerable to attacks that bypass localhost restrictions, enabling the creation of arbitrary packages
GHSA-2wcm-vx67-3x4q - HIGH Duplicate Advisory: GHSA-x698-5hjm-w2m5
GHSA-r9pp-r4xf-597r CVE-2024-39205 CRITICAL pyload-ng vulnerable to RCE with js2py sandbox escape
GHSA-25pw-q952-x37g - HIGH Duplicate Advisory: pyload-ng vulnerable to RCE with js2py sandbox escape
GHSA-pgpj-v85q-h5fm CVE-2024-22416 CRITICAL Cross-Site Request Forgery on any API call in pyLoad may lead to admin privilege escalation
GHSA-3f7w-p8vr-4v5f CVE-2024-32880 CRITICAL pyLoad allows upload to arbitrary folder lead to RCE
GHSA-g3cm-qg2v-2hj5 CVE-2024-24808 MODERATE pyLoad open redirect vulnerability due to improper validation of the is_safe_url function
GHSA-h73m-pcfw-25h2 CVE-2023-47890 HIGH Download to arbitrary folder can lead to RCE
GHSA-ghmw-rwh8-6qmr CVE-2024-21645 MODERATE pyload Log Injection vulnerability
GHSA-mqpq-2p68-46fv CVE-2024-21644 HIGH pyload Unauthenticated Flask Configuration Leakage vulnerability
GHSA-wcm6-wv95-7jw6 CVE-2023-0488 MODERATE Cross-site Scripting in pyload-ng
GHSA-6jmx-pv77-wm5w CVE-2023-0435 CRITICAL Excessive Attack Surface in pyload-ng
GHSA-8v53-23mx-hcf9 CVE-2023-0509 HIGH Improper Certificate Validation in pyload-ng
GHSA-pf38-5p22-x6h6 CVE-2023-0297 CRITICAL Code Injection in pyload-ng
GHSA-x9vc-5q77-m7x4 CVE-2023-0434 MODERATE Improper Input Validation in pyload-ng
GHSA-rv9x-wmw4-44qj CVE-2023-0227 MODERATE Pyload Insufficient Session Expiration vulnerability
GHSA-h8r9-467r-vjjf CVE-2023-0057 MODERATE pyLoad vulnerable to Improper Restriction of Rendered UI Layers or Frames
GHSA-m3g7-wrrq-v5c8 CVE-2023-0055 MODERATE Pyload contains Sensitive Cookie in HTTPS Session Without 'Secure' Attribute

GHSA-68w4-83fh-f2w8 at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-68w4-83fh-f2w8
Severity HIGH
CVSS v3.1 Base Score 8.1
CWE Classification CWE-287: Improper Authentication
Affected Product pyload-ng
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 8.1

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability None (A:N)

How to Fix GHSA-68w4-83fh-f2w8

To remediate this vulnerability, upgrade all affected pyload-ng dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

pyload-ng fixed: Latest secure release
pip install pyload-ng>=Latest secure release
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show pyload-ng

How to Check if You Are Affected

To determine whether your Python environment is affected by GHSA-68w4-83fh-f2w8, inspect your installed packages using pip:

pip show pyload-ng

Analyze the terminal output to locate any version of pyload-ng in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.

Note: The pip show pyload-ng command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-287: Improper Authentication, where when an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct. In pyload-ng, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary `Api.getUserData` (legacy) and `Api.get_userdata` are declared with `@permission(Perms.ANY)` and are reachable at `/api/getUserData` and `/api/get_userdata`. Because `Perms.ANY == 0` and pyLoad's permission check is a bitmask AND, that gate is a no-op: **every authenticated account passes, including one holding zero permission bits.** Both methods are thin wrappers around `check_auth()`, which the maintainers deliberately restricted to administrators by omitting `@permission` (no entry in `perm_map`, so `is_authorized()` returns `False` for non-admins). The wrappers undo that protection. An attacker holding the lowest-privileged account in the system therefore has a clean binary oracle on the **administrator password**, with no account lockout anywhere in the codebase, and with the 100 req/min rate limiter bypassable by rotating `X-Forwarded-For`. ## Affected code `src/pyload/core/api/__init__.py:1446` and `:1464` ```python #: Old API @permission(Perms.ANY) @get def getUserData(self, username: str, password: str) -> OldUserData: """ similar to `check_auth` but returns UserData type. """ user = self.check_auth(username, password) ... @permission(Perms.ANY) @get def get_userdata(self, username: str, password: str) -> UserData: user = self.check_auth(username, password) ... ``` ## Root cause `src/pyload/core/api/__init__.py:57` ```python class Perms(IntFlag): ANY = 0 #: requires no permission, but login ``` `src/pyload/core/api/__init__.py:108` ```python def has_permission(user_perms: Perms, required_perms: Perms): return required_perms == (user_perms & required_perms) ``` For `required_perms == 0` this evaluates to `0 == (user_perms & 0)` → `0 == 0` → **always `True`**. The `@permission(Perms.ANY)` gate therefore admits every authenticated principal regardless of which permission bits they hold. Contrast with the intended admin-only primitive, `src/pyload/core/api/__init__.py:1396`: ```python @legacy("checkAuth") @get def check_auth(self, username: str, password: str) -> dict[str, Any]: ``` `check_auth` has **no** `@permission`; `is_authorized()` at `api/__init__.py:1430` returns `False` for non-admins. The two wrappers carry `Perms.ANY` and restore access for everyone. Because both wrappers also carry `@get`, they are placed in `method_map` and are directly routable via `/api/<func>`. ## Amplifiers **1. No lockout.** There is no failed-login counter, delay, or ban anywhere in the codebase. A failed guess costs the attacker only one PBKDF2 computation. **2. Rate limiting is bypassable.** `/api/*` applies `rate_limit(count=100, period=60)` (`src/pyload/webui/app/blueprints/api_blueprint.py:25`), which buckets on a fully client-controlled header (`src/pyload/webui/app/helpers.py:446`): ```python client_ip = flask.request.headers.get("X-Forwarded-For", "").split(",")[0].strip() \ or flask.request.remote_addr ``` Rotating `X-Forwarded-For` per request yields a fresh bucket each time. Notably, `is_loopback_request()` in the **same file** (`helpers.py:288-294`) explicitly treats the presence of `X-Forwarded-For` / `X-Real-IP` / `Forwarded` as untrustworthy — the same guard was evidently not applied inside `rate_limit()`. ## Impact Online brute force of the administrator account leading to full administrative takeover. A successful response additionally discloses the target account's `id`, `name`, `email`, `role`, and `permission` bits. ## Proof of concept Verified against 0.5.0b3, commit `a5b008958`. Setup: stock instance with admin `pyload`, plus a non-admin user `bob` created with `role=USER` and `permission=0`. **Step 1 — establish that `bob` is genuinely unprivileged:** ``` GET /api/checkAuth?username=pyload&password=pyload -> 401 {"error": "Access denied"} GET /api/getAllUserData -> 401 {"error": "Access denied"} ``` **Step 2 — the flaw. Same user, same session, `Perms.ANY` gate:** ``` GET /api/getUserData?username=pyload&password=WRONG -> 200 {"name": null, "email": null, "role": null, "permission": null, "template_name": null} GET /api/getUserData?username=pyload&password=pyload -> 200 {"name": "pyload", "email": "", "role": 0, "permission": 0, "template_name": "default"} ``` `role: 0` is `Role.ADMIN`. `get_userdata` behaves identically. This is a perfect yes/no oracle. **Step 3 — measured brute force and recovery.** Run as `bob` (`permission = 0`), admin password set to a 2-character value, `X-Forwarded-For` rotated on every request: ``` attempts : 667 elapsed : 39.7s (16.8 guesses/sec) 429 rate-limits : 0 account lockout : NONE - same session authenticated throughout RECOVERED SECRET : 'zq' (true value 'zq') match=True ``` **Step 4 — full takeover with the recovered secret:** ``` POST /login as pyload/<recovered> -> HTTP 302 (authenticated as admin) GET /api/getAllUserData (admin-only) -> HTTP 200 (full user dump) ``` Measured throughput is 17-47 guesses/sec/thread. The only bound is PBKDF2-HMAC-SHA256 at 100,000 iterations in `src/pyload/core/database/user_database.py:14`, not any rate limit. ## Suggested remediation - Remove `@permission(Perms.ANY)` from `getUserData` and `get_userdata`, or drop them entirely — they are legacy compatibility shims, and modern callers already use the admin-only `check_auth`. - Structurally: `Perms.ANY = 0` makes `has_permission()` vacuous, so **any** method decorated `@permission(Perms.ANY)` silently becomes public to every logged-in user. Give `ANY` a real bit value, or handle it explicitly in `has_permission()` as "requires an authenticated session". - Do not trust `X-Forwarded-For` unless a trusted-proxy deployment is explicitly configured. Otherwise bucket `rate_limit()` on `request.remote_addr`, or add the same guard `is_loopback_request()` already uses. - Add per-account failed-authentication throttling or lockout. - Consider `hmac.compare_digest()` in `_check_password()` (`src/pyload/core/database/user_database.py:61` uses a plain `==` on the derived hash, contradicting the "always use compare_digest" guidance two files away). ## Notes for triage There is no `0.5.0b3` release on PyPI. The `develop` branch auto-publishes dev builds (`setup.py:86` appends `.dev<build>` to the `VERSION` file); the newest at time of testing was `0.5.0b3.dev101`. The `Perms.ANY = 0` design is long-standing, but only the `0.5.0b3.dev*` line was verified here — please confirm whether `0.5.0b2.*` and `0.4.x` are affected before finalizing the version range.

GHSA-68w4-83fh-f2w8 Affected pyload-ng Versions

1 component(s)
Product / Package Ecosystem Affected Versions Fixed Version
pyload-ng
PyPI
≥ 0.5.0b3.dev1
≤ 0.5.0b3.dev101

References

Frequently Asked Questions

What is GHSA-68w4-83fh-f2w8?
GHSA-68w4-83fh-f2w8 is a security vulnerability in pyload-ng involving getUserData/get_userdata exposed at Perms.ANY allow any authenticated account to brute-force the administrator password under CWE-287: Improper Authentication.
Which pyload-ng versions are affected?
Affected versions include releases prior to current release patches across affected packages: pyload-ng.
What version fixes GHSA-68w4-83fh-f2w8?
Upgrading to current release patches (or the latest patched release for your respective branch) resolves the vulnerability.
Is pyload-ng affected?
Yes, pyload-ng is affected in releases prior to Latest secure release. Upgrade your dependency to Latest secure release or later.
What is the CVSS score of GHSA-68w4-83fh-f2w8?
GHSA-68w4-83fh-f2w8 has a CVSS v3.1 base score of 8.1 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N.
What CWE is associated with GHSA-68w4-83fh-f2w8?
GHSA-68w4-83fh-f2w8 is classified under CWE-287: Improper Authentication (When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.).
How can I check PyPI dependencies for GHSA-68w4-83fh-f2w8?
Run the dependency tree command: 'pip show pyload-ng'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-68w4-83fh-f2w8",
  "modified" : "2026-10-09T17:09:15Z",
  "published" : "2026-10-09T17:09:15Z",
  "aliases" : [ ],
  "summary" : "pyload-ng: getUserData/get_userdata exposed at Perms.ANY allow any authenticated account to brute-force the administrator password",
  "details" : "## Summary\n\n`Api.getUserData` (legacy) and `Api.get_userdata` are declared with `@permission(Perms.ANY)` and are reachable at `/api/getUserData` and `/api/get_userdata`. Because `Perms.ANY == 0` and pyLoad's permission check is a bitmask AND, that gate is a no-op: **every authenticated account passes, including one holding zero permission bits.**\n\nBoth methods are thin wrappers around `check_auth()`, which the maintainers deliberately restricted to administrators by omitting `@permission` (no entry in `perm_map`, so `is_authorized()` returns `False` for non-admins). The wrappers undo that protection.\n\nAn attacker holding the lowest-privileged account in the system therefore has a clean binary oracle on the **administrator password**, with no account lockout anywhere in the codebase, and with the 100 req/min rate limiter bypassable by rotating `X-Forwarded-For`.\n\n## Affected code\n\n`src/pyload/core/api/__init__.py:1446` and `:1464`\n\n```python\n    #: Old API\n    @permission(Perms.ANY)\n    @get\n    def getUserData(self, username: str, password: str) -> OldUserData:\n        \"\"\"\n        similar to `check_auth` but returns UserData type.\n        \"\"\"\n        user = self.check_auth(username, password)\n        ...\n\n    @permission(Perms.ANY)\n    @get\n    def get_userdata(self, username: str, password: str) -> UserData:\n        user = self.check_auth(username, password)\n        ...\n```\n\n## Root cause\n\n`src/pyload/core/api/__init__.py:57`\n\n```python\nclass Perms(IntFlag):\n    ANY = 0  #: requires no permission, but login\n```\n\n`src/pyload/core/api/__init__.py:108`\n\n```python\ndef has_permission(user_perms: Perms, required_perms: Perms):\n    return required_perms == (user_perms & required_perms)\n```\n\nFor `required_perms == 0` this evaluates to `0 == (user_perms & 0)` → `0 == 0` → **always `True`**. The `@permission(Perms.ANY)` gate therefore admits every authenticated principal regardless of which permission bits they hold.\n\nContrast with the intended admin-only primitive, `src/pyload/core/api/__init__.py:1396`:\n\n```python\n    @legacy(\"checkAuth\")\n    @get\n    def check_auth(self, username: str, password: str) -> dict[str, Any]:\n```\n\n`check_auth` has **no** `@permission`; `is_authorized()` at `api/__init__.py:1430` returns `False` for non-admins. The two wrappers carry `Perms.ANY` and restore access for everyone.\n\nBecause both wrappers also carry `@get`, they are placed in `method_map` and are directly routable via `/api/<func>`.\n\n## Amplifiers\n\n**1. No lockout.** There is no failed-login counter, delay, or ban anywhere in the codebase. A failed guess costs the attacker only one PBKDF2 computation.\n\n**2. Rate limiting is bypassable.** `/api/*` applies `rate_limit(count=100, period=60)` (`src/pyload/webui/app/blueprints/api_blueprint.py:25`), which buckets on a fully client-controlled header (`src/pyload/webui/app/helpers.py:446`):\n\n```python\nclient_ip = flask.request.headers.get(\"X-Forwarded-For\", \"\").split(\",\")[0].strip() \\\n    or flask.request.remote_addr\n```\n\nRotating `X-Forwarded-For` per request yields a fresh bucket each time. Notably, `is_loopback_request()` in the **same file** (`helpers.py:288-294`) explicitly treats the presence of `X-Forwarded-For` / `X-Real-IP` / `Forwarded` as untrustworthy — the same guard was evidently not applied inside `rate_limit()`.\n\n## Impact\n\nOnline brute force of the administrator account leading to full administrative takeover. A successful response additionally discloses the target account's `id`, `name`, `email`, `role`, and `permission` bits.\n\n## Proof of concept\n\nVerified against 0.5.0b3, commit `a5b008958`.\n\nSetup: stock instance with admin `pyload`, plus a non-admin user `bob` created with `role=USER` and `permission=0`.\n\n**Step 1 — establish that `bob` is genuinely unprivileged:**\n\n```\nGET /api/checkAuth?username=pyload&password=pyload   ->  401 {\"error\": \"Access denied\"}\nGET /api/getAllUserData                              ->  401 {\"error\": \"Access denied\"}\n```\n\n**Step 2 — the flaw. Same user, same session, `Perms.ANY` gate:**\n\n```\nGET /api/getUserData?username=pyload&password=WRONG\n  -> 200 {\"name\": null, \"email\": null, \"role\": null, \"permission\": null, \"template_name\": null}\n\nGET /api/getUserData?username=pyload&password=pyload\n  -> 200 {\"name\": \"pyload\", \"email\": \"\", \"role\": 0, \"permission\": 0, \"template_name\": \"default\"}\n```\n\n`role: 0` is `Role.ADMIN`. `get_userdata` behaves identically. This is a perfect yes/no oracle.\n\n**Step 3 — measured brute force and recovery.** Run as `bob` (`permission = 0`), admin password set to a 2-character value, `X-Forwarded-For` rotated on every request:\n\n```\nattempts         : 667\nelapsed          : 39.7s   (16.8 guesses/sec)\n429 rate-limits  : 0\naccount lockout  : NONE - same session authenticated throughout\nRECOVERED SECRET : 'zq'   (true value 'zq')   match=True\n```\n\n**Step 4 — full takeover with the recovered secret:**\n\n```\nPOST /login as pyload/<recovered>       ->  HTTP 302  (authenticated as admin)\nGET  /api/getAllUserData (admin-only)  ->  HTTP 200  (full user dump)\n```\n\nMeasured throughput is 17-47 guesses/sec/thread. The only bound is PBKDF2-HMAC-SHA256 at 100,000 iterations in `src/pyload/core/database/user_database.py:14`, not any rate limit.\n\n## Suggested remediation\n\n- Remove `@permission(Perms.ANY)` from `getUserData` and `get_userdata`, or drop them entirely — they are legacy compatibility shims, and modern callers already use the admin-only `check_auth`.\n- Structurally: `Perms.ANY = 0` makes `has_permission()` vacuous, so **any** method decorated `@permission(Perms.ANY)` silently becomes public to every logged-in user. Give `ANY` a real bit value, or handle it explicitly in `has_permission()` as \"requires an authenticated session\".\n- Do not trust `X-Forwarded-For` unless a trusted-proxy deployment is explicitly configured. Otherwise bucket `rate_limit()` on `request.remote_addr`, or add the same guard `is_loopback_request()` already uses.\n- Add per-account failed-authentication throttling or lockout.\n- Consider `hmac.compare_digest()` in `_check_password()` (`src/pyload/core/database/user_database.py:61` uses a plain `==` on the derived hash, contradicting the \"always use compare_digest\" guidance two files away).\n\n## Notes for triage\n\nThere is no `0.5.0b3` release on PyPI. The `develop` branch auto-publishes dev builds (`setup.py:86` appends `.dev<build>` to the `VERSION` file); the newest at time of testing was `0.5.0b3.dev101`. The `Perms.ANY = 0` design is long-standing, but only the `0.5.0b3.dev*` line was verified here — please confirm whether `0.5.0b2.*` and `0.4.x` are affected before finalizing the version range.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "pyload-ng"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.5.0b3.dev1"
      }, {
        "last_affected" : "0.5.0b3.dev101"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/pyload/pyload/security/advisories/GHSA-68w4-83fh-f2w8"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pyload/pyload/commit/b99d2a2f06135363ceb0aab16aac5025f138e658"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/pyload/pyload"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-287" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-09T17:09:15Z",
    "nvd_published_at" : null
  }
}