CVE-2026-35459
Description
pyLoad is a free and open-source download manager written in Python. In 0.5.0b3.dev96 and earlier, pyLoad has a server-side request forgery (SSRF) vulnerability. The fix for CVE-2026-33992 added IP validation to BaseDownloader.download() that checks the hostname of the initial download URL. However, pycurl is configured with FOLLOWLOCATION=1 and MAXREDIRS=10, causing it to automatically follow HTTP redirects. Redirect targets are never validated against the SSRF filter. An authenticated user with ADD permission can bypass the SSRF fix by submitting a URL that redirects to an internal address.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pyload-ngPyPI | <= 0.5.0b3.dev96 | — |
Affected products
1- cpe:2.3:a:pyload-ng_project:pyload-ng:*:*:*:*:*:python:*:*Range: <0.5.0b3.dev97
Patches
133c55da08432fix GHSA-7gvf-3w72-p2pg security advisory
5 files changed · +26 −13
src/pyload/core/network/http/http_chunk.py+2 −0 modified@@ -133,6 +133,7 @@ def __init__(self, id, parent, range=None, resume=False): self.code = 0 #: last http code, set by parent + self.allow_private_ip = False self.aborted = False # indicates that the chunk aborted gracefully self.c = pycurl.Curl() @@ -189,6 +190,7 @@ def get_handle(self): ) self.c.setopt(pycurl.WRITEFUNCTION, self._write_body_callback) self.c.setopt(pycurl.HEADERFUNCTION, self._write_header_callback) + self.c.setopt(pycurl.PREREQFUNCTION, self._pre_request_callback) # request all bytes, since some servers in russia seems to have a defect # arithmetic unit
src/pyload/core/network/http/http_download.py+9 −2 modified@@ -1,11 +1,12 @@ import os import time +import urllib from logging import getLogger import pycurl from pyload import APPID -from ..exceptions import Abort +from ..exceptions import Abort, Fail from .http_chunk import ChunkInfo, HTTPChunk from .http_request import BadHeader @@ -142,8 +143,13 @@ def download(self, chunks=1, resume=False): self.close_chunk(chunk) return self._download(chunks, False) + elif code == 42: + hostname = urllib.parse.urlparse(self.url).hostname + raise Fail(f"Refusing to download from Server-Side host '{hostname}'") + else: raise + finally: self.close() @@ -239,7 +245,8 @@ def _download(self, chunks, resume): if errno != pycurl.E_WRITE_ERROR or not chunk.aborted: failed.append(chunk) ex = pycurl.error(errno, msg) - self.log.debug(f"Chunk {chunk.id + 1} failed: {ex}") + if errno != pycurl.E_ABORTED_BY_CALLBACK: + self.log.debug(f"Chunk {chunk.id + 1} failed: {ex}") continue try: #: check if the header implies success, else add it to failed list
src/pyload/core/network/http/http_request.py+13 −0 modified@@ -16,6 +16,7 @@ from ...utils.check import is_mapping from ...utils.convert import to_bytes, to_str +from ...utils.web.check import is_global_address from ...utils.web.parse import http_header as parse_header_line from ...utils.web.purge import unescape as html_unescape from ..exceptions import Abort @@ -71,6 +72,7 @@ class HTTPRequest: def __init__(self, cookies=None, options=None, limit=2_000_000): self.exception = None self.limit = limit + self.allow_private_ip = True self.c = pycurl.Curl() @@ -98,6 +100,7 @@ def __init__(self, cookies=None, options=None, limit=2_000_000): self.c.setopt(pycurl.WRITEFUNCTION, self._write_body_callback) self.c.setopt(pycurl.HEADERFUNCTION, self._write_header_callback) + self.c.setopt(pycurl.PREREQFUNCTION, self._pre_request_callback) self.log = getLogger(APPID) @@ -568,6 +571,16 @@ def _write_header_callback(self, buf): if self._header_buffer.endswith(b"\r\n\r\n"): self.response_headers.parse(self._header_buffer) + def _pre_request_callback(self, conn_primary_ip, conn_local_ip, conn_primary_port, conn_local_port): + """ + Called after TCP/TLS connection is established, before request is sent. + This runs for the initial request AND every redirect follow. + """ + if not self.allow_private_ip and not is_global_address(conn_primary_ip): + return pycurl.PREREQFUNC_ABORT # Aborts the entire transfer + + return pycurl.PREREQFUNC_OK + def add_header(self, name, value): """Append a value to a header name without replacing existing ones.""" self.request_headers.add(name, value)
src/pyload/core/utils/web/check.py+1 −1 modified@@ -3,7 +3,6 @@ import re import time -from ...network.request_factory import get_url from .convert import host_to_ip @@ -137,6 +136,7 @@ def get_public_address(addr_type="ipv4"): - str: The public IPv4 or IPv6 address as returned by the external service, or an empty string if all attempts fail. """ + from ...network.request_factory import get_url if addr_type == "ipv4": services = [ ("https://ipv4.icanhazip.com/", r"(\S+)"),
src/pyload/plugins/base/downloader.py+1 −10 modified@@ -6,8 +6,7 @@ from pyload.core.network.exceptions import Fail from pyload.core.network.http.exceptions import BadHeader from pyload.core.utils import format, fs, parse -from pyload.core.utils.web.check import is_global_address, is_ip_address -from pyload.core.utils.web.convert import host_to_ip +from pyload.core.utils.web.check import is_global_host from ..helpers import exists from .hoster import BaseHoster @@ -332,14 +331,6 @@ def download( ) self.check_status() - dl_hostname = urllib.parse.urlparse(dl_url).hostname - if is_ip_address(dl_hostname) and not is_global_address(dl_hostname): - self.fail(self._("Refusing to download from Server-Side host {}".format(dl_hostname))) - else: - for ip in host_to_ip(dl_hostname): - if not is_global_address(ip): - self.fail(self._("Refusing to download from Server-Side host {} ({})".format(dl_hostname, ip))) - newname = self._download( dl_url, dl_filename, get, post, referrer, cookies, disposition, resume, chunks )
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/pyload/pyload/commit/33c55da084320430edfd941b60e3da0eb1be9443nvdPatchWEB
- github.com/pyload/pyload/security/advisories/GHSA-7gvf-3w72-p2pgnvdExploitMitigationVendor AdvisoryWEB
- github.com/advisories/GHSA-7gvf-3w72-p2pgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33992ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-35459ghsaADVISORY
News mentions
0No linked articles in our index yet.