VAITP Dataset

← Back to the dataset

CVE-2026-46561

pyLoad authenticated SSRF via redirect allows internal network access.

  • CVSS 5.0
  • CWE-918
  • Input Validation and Sanitization
  • Remote

pyLoad is a free and open-source download manager written in Python. Prior to 0.5.0b3.dev100, the PREREQFUNCTION-based private IP check was not applied 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. This vulnerability is fixed in 0.5.0b3.dev100.

CVSS base score
5.0
Published
2026-05-28
OWASP
A10 Server-Side Request Forgery
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Input Validation and Sanitization
Subcategory
Server-Side Request Forgery (SSRF)
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
pyLoad
Fixed by upgrading
Yes

Solution

Upgrade to pyLoad version 0.5.0b3.dev100 or later.

Vulnerable code sample

import requests
import ipaddress
from urllib.parse import urlparse

def is_global_host(hostname):
    """
    Simplified check. Returns False for private, loopback, or reserved IPs.
    """
    if not hostname:
        return False
    try:
        ip = ipaddress.ip_address(hostname)
        return not (ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_unspecified)
    except ValueError:
        # If it's a domain name, assume it's global for the initial check.
        # This is where the bypass begins, as the resolved IP is not re-checked later.
        return True

def vulnerable_parse_urls(url):
    """
    Represents the vulnerable function that processes a URL.
    It checks the initial host but does not validate the host after a redirect.
    """
    hostname = urlparse(url).hostname

    # The check is only performed on the user-supplied URL's host.
    if not is_global_host(hostname):
        raise Exception(f"Access to non-global host '{hostname}' is forbidden.")

    try:
        # The requests library follows redirects by default (allow_redirects=True).
        # The vulnerability is that the 'Location' header of a 302 redirect
        # is not passed back through the is_global_host check.
        # An attacker can supply a public URL that redirects to an internal IP.
        response = requests.get(url, timeout=5)

        # In a real application, the content of the response would be processed.
        # This means code is executed based on the content of the internal,
        # redirected-to URL.
        return response.content
    except requests.RequestException as e:
        raise Exception(f"Failed to fetch URL: {e}")

Patched code sample

import ipaddress
from urllib.parse import urlparse, unwrap

# This is a simplified representation of pyLoad's `is_global_host` utility function.
# It returns False for private, reserved, or loopback IP addresses.
def is_global_host(hostname):
    """Checks if the given hostname corresponds to a public IP address."""
    if not hostname:
        return False
    try:
        # The unwrap function is used to handle IPv6-mapped IPv4 addresses like ::ffff:192.0.2.1
        ip = ipaddress.ip_address(unwrap(hostname))
        # The check ensures the IP is public (is_global) and not for multicast.
        return ip.is_global and not ip.is_multicast
    except ValueError:
        # If it's a domain name, not an IP, it's considered valid at this stage.
        # The actual IP resolution would be handled by the subsequent request.
        # The primary concern for this SSRF fix is blocking direct IP redirects.
        return True

# This function simulates the patched redirect handler in pyLoad's HTTPRequest class.
# The original vulnerability was that this function would follow the redirect
# without performing the `is_global_host` check on the new URL.
def handle_redirect_fixed(response_headers):
    """
    Simulates handling an HTTP 3xx redirect by validating the 'Location' header
    before following it, as per the fix for CVE-2023-46561.

    Args:
        response_headers (dict): A dictionary representing the HTTP response headers.

    Raises:
        ValueError: If the redirect points to a non-global/private address.
    """
    new_url = response_headers.get("Location")

    if new_url:
        hostname = urlparse(new_url).hostname

        # --- START OF THE FIX ---
        # The vulnerability was the absence of the following check. The application
        # would blindly follow the redirect, allowing an attacker to point it to
        # an internal network address (e.g., http://192.168.1.1/admin).
        #
        # The fix is to apply the same host validation to the redirect URL
        # that was applied to the initial user-supplied URL.
        if not is_global_host(hostname):
            raise ValueError(
                f"Redirect to non-global address '{hostname}' is not allowed."
            )
        # --- END OF THE FIX ---

        # If the check passes, the application would proceed to make a new
        # request to the `new_url`.
        # print(f"Redirect to safe host '{hostname}' is allowed.")
        pass

Payload

{
  "urls": ["http://attacker.example.com/redirect_to_internal"]
}

Cite this entry

@misc{vaitp:cve202646561,
  title        = {{pyLoad authenticated SSRF via redirect allows internal network access.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-46561},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-46561/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::