Medium

pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs

  • Disclosed: 09.07.2026
  • Updated: 11.10.2026 14:17

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":

python
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:

python
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::/16 traffic), have a NAT64 gateway, or otherwise resolve the IPv6 transition form to an internal IPv4 endpoint at the TCP layer.
  • PR:L — parse_urls (src/pyload/core/api/__init__.py:582) requires Perms.ADD, which any account capable of adding links holds. The curl PREREQFUNC at http_request.py:680 is reached by every downloader plugin that runs after is_global_host passed.
  • C:L/A:L — internal-network recon and timing-based confirmation; cloud-metadata exfiltration on networks where the transition form actually routes.

CWE-918: Server-Side Request Forgery (SSRF).

Affected versions

pyload-ng from the introduction of is_global_address / is_global_host in src/pyload/core/utils/web/check.py up to and including the current main HEAD as of filing.

Vulnerable code

src/pyload/core/utils/web/check.py:

python
def is_global_address(value):
    try:
        return ipaddress.ip_address(value).is_global
    except ValueError:
        return False

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.

Reproduction

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 and shows the connection would be ALLOWED in each case.

Suggested fix

Treat 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:

python
import ipaddress

_NAT64_WELL_KNOWN = ipaddress.IPv6Network("64:ff9b::/96")
_NAT64_DISCOVERY = ipaddress.IPv6Network("64:ff9b:1::/48")


def _embedded_ipv4(addr):
    if isinstance(addr, ipaddress.IPv6Address):
        if addr.ipv4_mapped is not None:
            return addr.ipv4_mapped
        if addr.sixtofour is not None:  # 2002::/16 6to4
            return addr.sixtofour
        if addr in _NAT64_WELL_KNOWN or addr in _NAT64_DISCOVERY:
            return ipaddress.IPv4Address(addr.packed[-4:])
    return None


def is_global_address(value):
    try:
        addr = ipaddress.ip_address(value)
    except ValueError:
        return False
    embedded = _embedded_ipv4(addr)
    if embedded is not None:
        addr = embedded
    return addr.is_global

A 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.

Credits

Reported by tonghuaroot.

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Python (PyPI) pyload-ng <= 0.5.0b3.dev100 no fix

References

CVSS score 4.9/10
CVSS vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:L
Weakness (CWE)
CWE-918
Platform
Python (PyPI)
Package
pyload-ng
Disclosed
09.07.2026
Updated
11.10.2026 14:17

Sources and licenses

OSV / GitHub Advisory Database - PYSEC-2026-2995 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-m5x5-28jr-gpjj - CC BY 4.0

Vulnerability history: pyload-ng

All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (58) in the archive
Total entries
58
First → latest
01.2023 10.2026
Without a fix
24
Exploited in attacks
0

Severity breakdown

  • Critical 9
  • High 23
  • Medium 25
  • Low 1

Disclosures per year

  1. 2023: 9
  2. 2024: 8
  3. 2025: 8
  4. 2026: 33
Severity:
  1. High · 7.5 ghsa-889w-m37p-88m5 >= 0.5.0b3.dev98, <= 0.5.0b3.dev101 no fix
  2. High · 8.1 ghsa-r44w-v6gf-x3p6 < 0.5.0b3.dev101 0.5.0b3.dev101
  3. pyLoad: SSRF guard bypass via IPv6 6to4/NAT64 transition wrappers of internal IPs this entry
    Medium · 4.9 CVE-2026-48737 <= 0.5.0b3.dev100 no fix
  4. High · 8.1 CVE-2026-42315 < 0.5.0b3.dev100 0.5.0b3.dev100
  5. High · 8.8 CVE-2026-35463 <= 0.5.0b3.dev96 no fix
  6. High · 7.7 CVE-2026-35187 <= 0.5.0b3.dev96 no fix
  7. High · 7.1 CVE-2026-29778 >= 0.5.0b3.dev13, < 0.5.0b3.dev97 0.5.0b3.dev97
  8. High · 8.1 CVE-2025-61773 < 0.5.0b3.dev91 0.5.0b3.dev91
  9. High CVE-2025-57751 < 0.5.0b3.dev92 0.5.0b3.dev92
Show more (13) Showing 20 of 33

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.