CVE-2026-48737: pyLoad SSRF Guard Bypass Vulnerability
MODERATECVE-2026-48737 is a MODERATE (CVSS 4.9) ssrf guard bypass vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting pyLoad libraries in the PyPI ecosystem. The issue involves ssrf guard bypass via ipv6 6to4/nat64 transition wrappers of internal ips. Remediation is available in the latest vendor maintenance releases.
CVE-2026-48737 at a Glance
Security Impact
CVSS: 4.9
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L).
How to Fix CVE-2026-48737
To remediate this vulnerability, upgrade all affected pyLoad dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install pyload-ng>=Latest secure release
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show pyload-ng
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-48737, inspect your installed packages using pip:
pip show pyload-ng
Analyze the terminal output to locate any version of pyLoad in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.
Note: The pip show pyload-ng command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In pyLoad, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-48737 Affected pyLoad Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
pyload-ng
|
PyPI |
≤ 0.5.0b3.dev100
|
|
References
Frequently Asked Questions
What is CVE-2026-48737?
Which pyLoad versions are affected?
What version fixes CVE-2026-48737?
Is pyload-ng affected?
What is the CVSS score of CVE-2026-48737?
What CWE is associated with CVE-2026-48737?
How can I check PyPI dependencies for CVE-2026-48737?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-m5x5-28jr-gpjj",
"modified" : "2026-07-09T13:35:22Z",
"published" : "2026-07-09T13:35:22Z",
"aliases" : [ "CVE-2026-48737" ],
"summary" : "pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs",
"details" : "## Summary\n\n`is_global_address` in [`src/pyload/core/utils/web/check.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/utils/web/check.py) is the central guard against SSRF-style outbound connections in pyload-ng. It tests whether a given IP is \"globally routable\" via Python's `ipaddress.ip_address(value).is_global`, and callers treat `not is_global` as \"deny\":\n\n```python\ndef is_global_address(value):\n try:\n return ipaddress.ip_address(value).is_global\n except ValueError:\n return False\n\ndef is_global_host(value):\n ips = host_to_ip(value)\n return ips and all((is_global_address(ip) for ip in ips))\n```\n\nPython's `ipaddress.IPv6Address.is_global` classifies the NAT64 well-known prefix as **globally routable** on every supported Python version (3.9 through 3.14 confirmed), and on older Pythons (3.9-3.11) the 6to4 prefix as well:\n\n| address | `is_global` on Py 3.9-3.11 | `is_global` on Py 3.12+ | wrapped IPv4 |\n|----------------------------------|---------------------------|--------------------------|----------------------|\n| `2002:7f00:0001::` (6to4) | True | False | 127.0.0.1 |\n| `2002:0a00:0001::` (6to4) | True | False | 10.0.0.1 |\n| `2002:a9fe:a9fe::` (6to4) | True | False | 169.254.169.254 (IMDS)|\n| `64:ff9b::a9fe:a9fe` (NAT64) | True | True | 169.254.169.254 |\n| `64:ff9b::7f00:1` (NAT64) | True | True | 127.0.0.1 |\n\npyload-ng declares `python_requires = >=3.9` (`setup.cfg`), so deployments on Python 3.9-3.11 see the 6to4 path too. The NAT64 path is universal. `is_global` returns True for these wrappers, so `is_global_address` returns True and the deny check passes. The pycurl `PREREQFUNC` at [`src/pyload/core/network/http/http_request.py:680`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/network/http/http_request.py#L680) consults the same helper just before TCP-connect:\n\n```python\nif not self.allow_private_ip:\n is_proxy_ip = self.http_proxy_host and self.http_proxy_host == (conn_primary_ip, conn_primary_port)\n if not is_global_address(conn_primary_ip) and not is_proxy_ip:\n return pycurl.PREREQFUNC_ABORT\nreturn pycurl.PREREQFUNC_OK\n```\n\nOn a host with 6to4 routing (legacy operator tunnels; `2002::/16` still configurable) or NAT64 (cloud IPv6-only subnets with NAT64 gateway), the encoded form routes to the embedded IPv4 and the curl connection terminates at the internal endpoint, defeating the deny.\n\n`is_global_host` (the helper that callers like `parse_urls` use against a URL hostname) feeds through `host_to_ip` which pins `family=AF_INET`, so hostname-based reach to these forms relies on the attacker supplying an IPv6 literal in the URL — but the curl PREREQFUNC sees the actual resolved IP (the AAAA returned for the hostname), so a hostname with an AAAA record set to one of the bypass forms reaches the same gap.\n\nCross-reference: this is the same incomplete-coverage class as pydantic-ai's [GHSA-cqp8-fcvh-x7r3](https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cqp8-fcvh-x7r3) / CVE-2026-46678. pyload-ng's prior SSRF advisories [GHSA-7gvf-3w72-p2pg](https://github.com/pyload/pyload/security/advisories/GHSA-7gvf-3w72-p2pg) and [GHSA-8rp3-xc6w-5qp5](https://github.com/pyload/pyload/security/advisories/GHSA-8rp3-xc6w-5qp5) both went through `is_global_host` / `is_global_address`; the IPv6 transition gap is orthogonal to those redirect-bypass classes.\n\n## Severity\n\n**MEDIUM** — `CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L` = **4.7**\n\n- `AC:H` — exploitation requires the host network to route 6to4 (`2002::/16` traffic), have a NAT64 gateway, or otherwise resolve the IPv6 transition form to an internal IPv4 endpoint at the TCP layer.\n- `PR:L` — `parse_urls` ([`src/pyload/core/api/__init__.py:582`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/api/__init__.py#L582)) requires `Perms.ADD`, which any account capable of adding links holds. The curl PREREQFUNC at [`http_request.py:680`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/http_request.py#L680) is reached by every downloader plugin that runs after `is_global_host` passed.\n- `C:L/A:L` — internal-network recon and timing-based confirmation; cloud-metadata exfiltration on networks where the transition form actually routes.\n\n**CWE-918**: Server-Side Request Forgery (SSRF).\n\n## Affected versions\n\n`pyload-ng` from the introduction of `is_global_address` / `is_global_host` in [`src/pyload/core/utils/web/check.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/utils/web/check.py) up to and including the current main HEAD as of filing.\n\n## Vulnerable code\n\n[`src/pyload/core/utils/web/check.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/src/pyload/core/utils/web/check.py):\n\n```python\ndef is_global_address(value):\n try:\n return ipaddress.ip_address(value).is_global\n except ValueError:\n return False\n```\n\n`Python ipaddress.IPv6Address.is_global` returns True for every address in `2002::/16` (6to4) and `64:ff9b::/96` (NAT64) regardless of the IPv4 they wrap, so this guard is a one-line bypass for the prefix the attacker chooses.\n\n## Reproduction\n\n[`research_wave5/poc/pyload_ipv6_ssrf/poc.py`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/research_wave5/poc/pyload_ipv6_ssrf/poc.py) drives both `is_global_address` and `is_global_host` against IPv6 transition forms whose embedded IPv4 points at loopback, RFC 1918, and AWS IMDS. The helper returns \"globally routable\" for every form. A second pass replays the same forms through the `PREREQFUNC` logic in [`http_request.py:680`](https://github.com/pyload/pyload/blob/1b12dc7f348db8c144e0f39215680415e90ca4d2/http_request.py#L680) and shows the connection would be ALLOWED in each case.\n\n## Suggested fix\n\nTreat IPv6 transition-encoding forms by unwrapping the embedded IPv4 and re-running the global check, plus an explicit blocklist of well-known embedding prefixes for defence in depth:\n\n```python\nimport ipaddress\n\n_NAT64_WELL_KNOWN = ipaddress.IPv6Network(\"64:ff9b::/96\")\n_NAT64_DISCOVERY = ipaddress.IPv6Network(\"64:ff9b:1::/48\")\n\n\ndef _embedded_ipv4(addr):\n if isinstance(addr, ipaddress.IPv6Address):\n if addr.ipv4_mapped is not None:\n return addr.ipv4_mapped\n if addr.sixtofour is not None: # 2002::/16 6to4\n return addr.sixtofour\n if addr in _NAT64_WELL_KNOWN or addr in _NAT64_DISCOVERY:\n return ipaddress.IPv4Address(addr.packed[-4:])\n return None\n\n\ndef is_global_address(value):\n try:\n addr = ipaddress.ip_address(value)\n except ValueError:\n return False\n embedded = _embedded_ipv4(addr)\n if embedded is not None:\n addr = embedded\n return addr.is_global\n```\n\nA patch implementing this approach (plus tests covering 6to4 and NAT64 wraps for 127.0.0.1, 10.0.0.1, 172.16.0.1, 192.168.1.1, 100.64.0.0/10, and 169.254.169.254) accompanies the fix PR.\n\n## Credits\n\nReported by tonghuaroot.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "pyload-ng"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "0.5.0b3.dev100"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/pyload/pyload/security/advisories/GHSA-m5x5-28jr-gpjj"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/pyload/pyload"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-918" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-09T13:35:22Z",
"nvd_published_at" : null
}
}