GHSA-r44w-v6gf-x3p6
pyLoad has an authentication bypass in API key validation (check_apikey cache)
- Severity
- 8.1 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- —
- No score published
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-287 Improper Authentication
- Fix
0.5.0b3.dev101- Upgrade pyload-ng to this version or later
Description
Summary
The API-key cache in check_apikey() allows an attacker to authenticate with a forged API key as long as the legitimate key for the same key_id has been used recently.
On a cache hit, the API key secret is never verified, resulting in a complete authentication bypass during the cache lifetime.
Affected Components
-
src/pyload/core/api/init.pyApi.check_apikey()
-
src/pyload/core/database/apikey_database.pycheck_apikey()_check_key()
The cache is used by normal API requests through:
src/pyload/webui/app/helpers.py
which calls:
api.check_apikey(api_key)
without providing a ttl, meaning the default 300-second cache is always enabled.
Root Cause
Successful API key validations are cached in:
self._apikey_cache
using the following structure:
{
key_id: (timestamp, cached_data)
}
The cache key is based only on key_id, and that value is parsed directly from the user-supplied API key rather than being obtained from a trusted lookup.
When a cache hit occurs within the TTL, check_apikey() returns:
{
"success": True,
"data": cached_data
}
after checking only:
expires_at
At no point on this execution path is the supplied secret:
apikey[-43:]
compared against the stored key hash.
The actual secret verification exists only in:
_check_key()
which correctly uses:
hmac.compare_digest()
However, this function is only reached on a cache miss through the database layer's check_apikey() implementation.
Furthermore, the cached object cannot be used for verification because it intentionally does not include the stored key hash.
The cached record contains only:
iduser_idnamecreated_atexpires_atlast_used
Since key_hash is absent, the cache lacks the information required to validate the presented secret.
As a result, once a legitimate request populates the cache, every subsequent request using the same key_id within the 300-second cache window is accepted without validating the secret.
Impact
key_id values are small sequential integers assigned when API keys are created (1, 2, 3, ...), making them trivial to enumerate.
The API key format is also publicly reconstructable from the source code.
An attacker therefore does not need to know any valid API key secret.
They only need a key_id whose legitimate key has been used within the previous five minutes, which is likely on an actively used deployment.
During that cache window, a forged API key follows exactly the same authentication path as a legitimate key and inherits the associated user's permissions.
This results in a complete remote authentication bypass.
I would classify the issue as Critical severity.
Estimated CVSS v3.1
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
(AC:H only because successful exploitation depends on the cache already containing an entry.)
Reproduction
1. Create an administrator account.
2. Generate an API key.
Example:
pl_11<43-character-secret>
where:
key_id = 1
3. Send a legitimate authenticated request.
curl \
-H "X-API-Key: pl_11<real-43-char-secret>" \
http://127.0.0.1:8000/api/status
Expected response:
HTTP/1.1 200 OK
4. Within 300 seconds, send the same request using a forged API key.
curl \
-H "X-API-Key: pl_11xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
http://127.0.0.1:8000/api/status
Observed response:
HTTP/1.1 200 OK
The request succeeds even though the API key secret is entirely incorrect.
Control Test
If the forged API key is sent before any legitimate request has populated the cache, the server correctly returns:
HTTP/1.1 401 Unauthorized
The only difference between the failing and succeeding forged request is whether the cache already contains an entry for that key_id.
This isolates the vulnerability specifically to the cache-hit path.
Suggested Fix
Cache hits should never replace secret verification.
Possible approaches include:
-
Store a value derived from the complete API key (or its hash) in the cache and verify it using
hmac.compare_digest()on every cache hit before returning success. -
Alternatively, key the cache using a value derived from the entire API key secret (for example, a cryptographic hash of the complete API key) rather than using only
key_id.
Regardless of implementation, the security invariant should remain:
The API key secret must be validated on every authenticated request, regardless of whether the cache is hit or missed.
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| pyload-ng | < 0.5.0b3.dev101 |
0.5.0b3.dev101 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
pyload-ng PyPI → 0.5.0b3.dev101
- pip
pip install --upgrade "pyload-ng>=0.5.0b3.dev101"- poetry
poetry add "pyload-ng>=0.5.0b3.dev101"- uv
uv add "pyload-ng>=0.5.0b3.dev101"
Severity breakdown
CVSS 3.1 8.1
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Weakness
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 to prevent it
- Architecture and Design: Use an authentication framework or library such as the OWASP ESAPI Authentication feature.
References
Packages
Other links
Timeline
- Published
- Last updated by the source
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-68w4-83fh-f2w8 pyload-ng: getUserData/get_userdata exposed at Perms.ANY allow any authenticated account to brute-force the administrator password | High | Also affects pyload-ng |
| GHSA-9q47-3cm2-2rp8 pyLoad: Rate-Limit Bypass and Audit-Log Spoofing via Trusted Client-Controlled `X-Forwarded-For` Header | Medium | Also affects pyload-ng |
| GHSA-jq7h-wrvp-3rgx pyLoad: Privilege revocation and password change through the REST API do not invalidate the user's session | High | Also affects pyload-ng |
| GHSA-889w-m37p-88m5 pyLoad: Api.set_user_permission never invalidates the target's session | High | Also affects pyload-ng |
| GHSA-p3pr-8f3m-4qp8 pyLoad WindowsPhoneNotify addon: non-admin SETTINGS user triggers SSRF via unguarded http.client notification host | Medium | Also affects pyload-ng |
| GHSA-fr26-jjhm-638c pyLoad: Tar extraction creates device nodes and FIFOs (member types not filtered; tarfile extractall without filter=) | High | Also affects pyload-ng |
| GHSA-j92p-c242-7hfx pyLoad: Unauthenticated access to /web/<path:filename> bypasses authentication on sensitive templates and leaks internal error details via exception attribute typo | Medium | Also affects pyload-ng |
| GHSA-vq8p-m3wm-gv5f pyLoad: Lack of Input Size Validation Leads to Denial of Service (DoS) and Process Termination | Medium | Also affects pyload-ng |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor