CVE-2026-48737
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, is_global_address in src/pyload/core/utils/web/check.py relies on Python's global-address classification without examining IPv4 destinations embedded in 6to4 or NAT64 IPv6 addresses. A low-privileged user can submit an IPv6 literal through parse_urls to the pre-resolution is_global_host guard. Because host_to_ip is pinned to AF_INET, that guard does not evaluate a hostname's AAAA result. Separately, curl resolves hostnames before the pycurl PREREQFUNC in src/pyload/core/network/http/http_request.py applies the same vulnerable is_global_address check to the actual connection address, so a transition-form AAAA result can be permitted even when it terminates at an embedded loopback, private, CGNAT, or link-local IPv4 address. Exploitation requires the pyLoad host to route the applicable transition mechanism, including 6to4 on affected Python 3.9 through 3.11 deployments or NAT64 on a network with a NAT64 gateway. Successful exploitation can enable internal-network reconnaissance, timing-based confirmation, limited service disruption, or cloud metadata disclosure where the wrapped address is routable. This issue is fixed in version 0.5.0b3.dev101.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
4.9
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, is_global_address in src/pyload/core/utils/web/check.py relies on Python's global-address classification without examining IPv4 destinations embedded in 6to4 or NAT64 IPv6 addresses. A low-privileged user can submit an IPv6 literal through parse_urls to the pre-resolution is_global_host guard. Because host_to_ip is pinned to AF_INET, that guard does not evaluate a hostname's AAAA result. Separately, curl resolves hostnames before the pycurl PREREQFUNC in src/pyload/core/network/http/http_request.py applies the same vulnerable is_global_address check to the actual connection address, so a transition-form AAAA result can be permitted even when it terminates at an embedded loopback, private, CGNAT, or link-local IPv4 address. Exploitation requires the pyLoad host to route the applicable transition mechanism, including 6to4 on affected Python 3.9 through 3.11 deployments or NAT64 on a network with a NAT64 gateway. Successful exploitation can enable internal-network reconnaissance, timing-based confirmation, limited service disruption, or cloud metadata disclosure where the wrapped address is routable. This issue is fixed in version 0.5.0b3.dev101.
- Record
CVE-2026-48737- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-78593
4.9
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, is_global_address in src/pyload/core/utils/web/check.py relies on Python's global-address classification without examining IPv4 destinations embedded in 6to4 or NAT64 IPv6 addresses. A low-privileged user can submit an IPv6 literal through parse_urls to the pre-resolution is_global_host guard. Because host_to_ip is pinned to AF_INET, that guard does not evaluate a hostname's AAAA result. Separately, curl resolves hostnames before the pycurl PREREQFUNC in src/pyload/core/network/http/http_request.py applies the same vulnerable is_global_address check to the actual connection address, so a transition-form AAAA result can be permitted even when it terminates at an embedded loopback, private, CGNAT, or link-local IPv4 address. Exploitation requires the pyLoad host to route the applicable transition mechanism, including 6to4 on affected Python 3.9 through 3.11 deployments or NAT64 on a network with a NAT64 gateway. Successful exploitation can enable internal-network reconnaissance, timing-based confirmation, limited service disruption, or cloud metadata disclosure where the wrapped address is routable. This issue is fixed in version 0.5.0b3.dev101.
- Record
EUVD-2026-78593- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-m5x5-28jr-gpjj
4.9
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
Summary
is_global_address in 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":
def is_global_address(value):
try:
return ipaddress.ip_address(value).is_global
except ValueError:
return False
def is_global_host(value):
ips = host_to_ip(value)
return ips and all((is_global_address(ip) for ip in ips))
Python'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:
| address | is_global on Py 3.9-3.11 | is_global on Py 3.12+ | wrapped IPv4 |
|----------------------------------|---------------------------|--------------------------|----------------------|
| 2002:7f00:0001:: (6to4) | True | False | 127.0.0.1 |
| 2002:0a00:0001:: (6to4) | True | False | 10.0.0.1 |
| 2002:a9fe:a9fe:: (6to4) | True | False | 169.254.169.254 (IMDS)|
| 64:ff9b::a9fe:a9fe (NAT64) | True | True | 169.254.169.254 |
| 64:ff9b::7f00:1 (NAT64) | True | True | 127.0.0.1 |
pyload-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 consults the same helper just before TCP-connect:
if not self.allow_private_ip:
is_proxy_ip = self.http_proxy_host and self.http_proxy_host == (conn_primary_ip, conn_primary_port)
if not is_global_address(conn_primary_ip) and not is_proxy_ip:
return pycurl.PREREQFUNC_ABORT
return pycurl.PREREQFUNC_OK
On 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.
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.
Cross-reference: this is the same incomplete-coverage class as pydantic-ai's GHSA-cqp8-fcvh-x7r3 / CVE-2026-46678. pyload-ng's prior SSRF advisories GHSA-7gvf-3w72-p2pg and GHSA-8rp3-xc6w-5qp5 both went through is_global_host / is_global_address; the IPv6 transition gap is orthogonal to those redirect-bypass classes.
Severity
MEDIUM — CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L = 4.7
AC:H— exploitation requires the host network to route 6to4 (2002::/16traffic), have a NAT64 gateway, or otherwise resolve the IPv6…
- Record
GHSA-m5x5-28jr-gpjj- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
4.9
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, is_global_address in src/pyload/core/utils/web/check.py relies on Python's global-address classification without examining IPv4 destinations embedded in 6to4 or NAT64 IPv6 addresses. A low-privileged user can submit an IPv6 literal through parse_urls to the pre-resolution is_global_host guard. Because host_to_ip is pinned to AF_INET, that guard does not evaluate a hostname's AAAA result. Separately, curl resolves hostnames before the pycurl PREREQFUNC in src/pyload/core/network/http/http_request.py applies the same vulnerable is_global_address check to the actual connection address, so a transition-form AAAA result can be permitted even when it terminates at an embedded loopback, private, CGNAT, or link-local IPv4 address. Exploitation requires the pyLoad host to route the applicable transition mechanism, including 6to4 on affected Python 3.9 through 3.11 deployments or NAT64 on a network with a NAT64 gateway. Successful exploitation can enable internal-network reconnaissance, timing-based confirmation, limited service disruption, or cloud metadata disclosure where the wrapped address is routable. This issue is fixed in version 0.5.0b3.dev101.
- Record
CVE-2026-48737- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
4.9
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev101, is_global_address in src/pyload/core/utils/web/check.py relies on Python's global-address classification without examining IPv4 destinations embedded in 6to4 or NAT64 IPv6 addresses. A low-privileged user can submit an IPv6 literal through parse_urls to the pre-resolution is_global_host guard. Because host_to_ip is pinned to AF_INET, that guard does not evaluate a hostname's AAAA result. Separately, curl resolves hostnames before the pycurl PREREQFUNC in src/pyload/core/network/http/http_request.py applies the same vulnerable is_global_address check to the actual connection address, so a transition-form AAAA result can be permitted even when it terminates at an embedded loopback, private, CGNAT, or link-local IPv4 address. Exploitation requires the pyLoad host to route the applicable transition mechanism, including 6to4 on affected Python 3.9 through 3.11 deployments or NAT64 on a network with a NAT64 gateway. Successful exploitation can enable internal-network reconnaissance, timing-based confirmation, limited service disruption, or cloud metadata disclosure where the wrapped address is routable. This issue is fixed in version 0.5.0b3.dev101.
- Record
CVE-2026-48737- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-m5x5-28jr-gpjj
4.9
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
Summary
is_global_address in 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":
def is_global_address(value):
try:
return ipaddress.ip_address(value).is_global
except ValueError:
return False
def is_global_host(value):
ips = host_to_ip(value)
return ips and all((is_global_address(ip) for ip in ips))
Python'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:
| address | is_global on Py 3.9-3.11 | is_global on Py 3.12+ | wrapped IPv4 |
|----------------------------------|---------------------------|--------------------------|----------------------|
| 2002:7f00:0001:: (6to4) | True | False | 127.0.0.1 |
| 2002:0a00:0001:: (6to4) | True | False | 10.0.0.1 |
| 2002:a9fe:a9fe:: (6to4) | True | False | 169.254.169.254 (IMDS)|
| 64:ff9b::a9fe:a9fe (NAT64) | True | True | 169.254.169.254 |
| 64:ff9b::7f00:1 (NAT64) | True | True | 127.0.0.1 |
pyload-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 consults the same helper just before TCP-connect:
if not self.allow_private_ip:
is_proxy_ip = self.http_proxy_host and self.http_proxy_host == (conn_primary_ip, conn_primary_port)
if not is_global_address(conn_primary_ip) and not is_proxy_ip:
return pycurl.PREREQFUNC_ABORT
return pycurl.PREREQFUNC_OK
On 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.
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.
Cross-reference: this is the same incomplete-coverage class as pydantic-ai's GHSA-cqp8-fcvh-x7r3 / CVE-2026-46678. pyload-ng's prior SSRF advisories GHSA-7gvf-3w72-p2pg and GHSA-8rp3-xc6w-5qp5 both went through is_global_host / is_global_address; the IPv6 transition gap is orthogonal to those redirect-bypass classes.
Severity
MEDIUM — CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L = 4.7
AC:H— exploitation requires the host network to route 6to4 (2002::/16traffic), have a NAT64 gateway, or otherwise resolve the IPv6…
- Record
GHSA-m5x5-28jr-gpjj- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVPYSEC-2026-2995
4.9
pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs
Summary
is_global_address in 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":
def is_global_address(value):
try:
return ipaddress.ip_address(value).is_global
except ValueError:
return False
def is_global_host(value):
ips = host_to_ip(value)
return ips and all((is_global_address(ip) for ip in ips))
Python'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:
| address | is_global on Py 3.9-3.11 | is_global on Py 3.12+ | wrapped IPv4 |
|----------------------------------|---------------------------|--------------------------|----------------------|
| 2002:7f00:0001:: (6to4) | True | False | 127.0.0.1 |
| 2002:0a00:0001:: (6to4) | True | False | 10.0.0.1 |
| 2002:a9fe:a9fe:: (6to4) | True | False | 169.254.169.254 (IMDS)|
| 64:ff9b::a9fe:a9fe (NAT64) | True | True | 169.254.169.254 |
| 64:ff9b::7f00:1 (NAT64) | True | True | 127.0.0.1 |
pyload-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 consults the same helper just before TCP-connect:
if not self.allow_private_ip:
is_proxy_ip = self.http_proxy_host and self.http_proxy_host == (conn_primary_ip, conn_primary_port)
if not is_global_address(conn_primary_ip) and not is_proxy_ip:
return pycurl.PREREQFUNC_ABORT
return pycurl.PREREQFUNC_OK
On 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.
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.
Cross-reference: this is the same incomplete-coverage class as pydantic-ai's GHSA-cqp8-fcvh-x7r3 / CVE-2026-46678. pyload-ng's prior SSRF advisories GHSA-7gvf-3w72-p2pg and GHSA-8rp3-xc6w-5qp5 both went through is_global_host / is_global_address; the IPv6 transition gap is orthogonal to those redirect-bypass classes.
Severity
MEDIUM — CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L = 4.7
AC:H— exploitation requires the host network to route 6to4 (2002::/16traffic), have a NAT64 gateway, or otherwise resolve the IPv6…
- Record
PYSEC-2026-2995- CVSS 3.1
- 4.9 Medium
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pyload-ngPyPI |
through 0.5.0b3.dev100 | None known |
pyload-ngPyPI |
through 0.5.0b3.dev100, plus listed versions 0.5.0a5.dev5280.5.0a5.dev5320.5.0a5.dev5350.5.0a5.dev5360.5.0a5.dev537 Show 45 moreShow less0.5.0a5.dev5390.5.0a5.dev5400.5.0a5.dev5450.5.0a5.dev5620.5.0a5.dev5640.5.0a5.dev5650.5.0a6.dev5700.5.0a6.dev5780.5.0a6.dev5870.5.0a7.dev5960.5.0a8.dev6020.5.0a9.dev6150.5.0a9.dev6290.5.0a9.dev6320.5.0a9.dev6410.5.0a9.dev6430.5.0a9.dev6550.5.0a9.dev8060.5.0b1.dev10.5.0b1.dev20.5.0b1.dev30.5.0b1.dev40.5.0b1.dev50.5.0b2.dev100.5.0b2.dev110.5.0b2.dev120.5.0b2.dev90.5.0b3.dev130.5.0b3.dev140.5.0b3.dev170.5.0b3.dev180.5.0b3.dev190.5.0b3.dev200.5.0b3.dev210.5.0b3.dev220.5.0b3.dev240.5.0b3.dev260.5.0b3.dev270.5.0b3.dev280.5.0b3.dev290.5.0b3.dev300.5.0b3.dev310.5.0b3.dev320.5.0b3.dev330.5.0b3.dev34 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-48737 is a medium vulnerability (CVSS 4.9) in Pyload, published July 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.29%, the 21st percentile. No source names a fixed version.
References
github.com/pyload/pyload/security/advisories/GHSA-m5x5-28jr-gpjj
github.com/pyload/pyload/commit/ba7491046b2d430732be490913e4cbf1f1e899c2
github.com/advisories/GHSA-m5x5-28jr-gpjj
github.com/CVEProject/cvelistV5/tree/main/cves/2026/48xxx/CVE-2026-48737.json
nvd.nist.gov/vuln/detail/CVE-2026-48737
github.com/pyload/pyload
pypi.org/project/pyload-ng
Left out here: 54 versions and the rest of 3 source descriptions. The JSON record has them.