VAITP Dataset

← Back to the dataset

CVE-2026-24052

Insufficient URL validation allows requests to attacker-controlled domains.

  • CVSS 7.1
  • CWE-601
  • Input Validation and Sanitization
  • Remote

Claude Code is an agentic coding tool. Prior to version 1.0.111, Claude Code contained insufficient URL validation in its trusted domain verification mechanism for WebFetch requests. The application used a startsWith() function to validate trusted domains (e.g., docs.python.org, modelcontextprotocol.io), this could have enabled attackers to register domains like modelcontextprotocol.io.example.com that would pass validation. This could enable automatic requests to attacker-controlled domains without user consent, potentially leading to data exfiltration. This issue has been patched in version 1.0.111.

CVSS base score
7.1
Published
2026-02-03
OWASP
A10 Server-Side Request Forgery
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Server-Side Request Forgery (SSRF)
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
Claude Code
Fixed by upgrading
Yes

Solution

Upgrade to version 1.0.111.

Vulnerable code sample

import urllib.parse

# This list represents the domains the tool is allowed to contact automatically.
TRUSTED_DOMAINS = [
    "docs.python.org",
    "modelcontextprotocol.io",
]

def make_webfetch_request(url: str):
    """
    Simulates an agentic tool making a web request after a security check.
    This function contains the validation vulnerability described in CVE-2026-24052.
    """
    try:
        hostname = urllib.parse.urlparse(url).hostname
    except Exception:
        hostname = None

    if not hostname:
        # Silently fail or log if URL is malformed
        return

    is_trusted = False
    for trusted_domain in TRUSTED_DOMAINS:
        # VULNERABILITY: The check incorrectly uses startswith() on the hostname.
        # This allows a domain like 'modelcontextprotocol.io.example.com'
        # to pass validation because it starts with 'modelcontextprotocol.io'.
        # A correct check would validate the entire domain or use a more robust
        # method to prevent subdomain-like attacks.
        if hostname.startswith(trusted_domain):
            is_trusted = True
            break
    
    if is_trusted:
        # In a real application, this would trigger an actual network request.
        # This is the step where a request could be sent to an attacker's server.
        print(f"Validation passed. Initiating automatic request to: {url}")
    else:
        # This is the expected behavior for untrusted domains.
        print(f"Blocked request to untrusted domain: {url}")

Patched code sample

import sys
from urllib.parse import urlparse

def is_url_trusted_fixed(url: str, trusted_domains: list[str]) -> bool:
    """
    Demonstrates the patched logic to fix the vulnerability described in
    CVE-2026-24052.

    This function validates if a URL's hostname is trusted by checking for an
    exact match or a valid subdomain. It avoids the flawed `startswith()`
    check that allowed malicious domains like 'trusted.com.evil.com' to pass.

    The correct logic is to verify that the hostname is either an exact match
    to a trusted domain or that it is a subdomain ending with '.<trusted_domain>'.

    Args:
        url: The URL string to validate.
        trusted_domains: A list of trusted base domains.

    Returns:
        True if the URL's hostname is trusted, False otherwise.
    """
    if not isinstance(url, str):
        return False

    try:
        # Safely parse the URL to extract the hostname.
        hostname = urlparse(url).hostname
        if not hostname:
            return False
    except (ValueError, AttributeError):
        # Handle malformed URLs or cases where parsing fails.
        return False

    # Normalize hostname to prevent case-related bypasses.
    hostname = hostname.lower()

    # THE FIX:
    # Iterate through the trusted domains and perform a robust check.
    # A domain is trusted if:
    # 1. The hostname is an exact match for a trusted domain.
    # 2. The hostname is a subdomain of a trusted domain.
    for domain in trusted_domains:
        normalized_domain = domain.lower()
        if hostname == normalized_domain or hostname.endswith(f".{normalized_domain}"):
            return True

    # If no match is found after checking all trusted domains, deny the request.
    return False

# --- Demonstration of the fix in action ---
if __name__ == '__main__':
    # According to the CVE, these are examples of trusted domains.
    TRUSTED_DOMAINS = [
        "docs.python.org",
        "modelcontextprotocol.io"
    ]

    # List of URLs to test against the patched validation logic.
    test_urls = {
        # --- URLs that should be ALLOWED ---
        "Valid (Exact Match 1)": "https://docs.python.org/3/library/urllib.parse.html",
        "Valid (Exact Match 2)": "https://modelcontextprotocol.io/v1/spec",
        "Valid (Subdomain)": "https://sub.docs.python.org/path/to/resource",

        # --- URLs that must be BLOCKED by the fix ---
        "Vulnerable (CVE Vector)": "https://modelcontextprotocol.io.example.com/malicious",
        "Vulnerable (Similar)": "https://docs.python.org.malicious-site.net/page",
        "Vulnerable (No dot)": "https://docs.python.org-fake.com/",

        # --- Other untrusted URLs that should be BLOCKED ---
        "Untrusted (Different)": "https://www.example.com",
        "Malformed (No Host)": "ftp://user@/path",
        "Invalid Scheme": "my-app-scheme://modelcontextprotocol.io/data"
    }

    print(f"--- Demonstrating Patched URL Validation (CVE-2026-24052 Fix) ---")
    print(f"Trusted Domains: {TRUSTED_DOMAINS}\n")

    has_failures = False
    for description, url_to_test in test_urls.items():
        is_trusted = is_url_trusted_fixed(url_to_test, TRUSTED_DOMAINS)
        status = "ALLOWED" if is_trusted else "BLOCKED"
        
        # Determine expected outcome based on description
        expected_status = "ALLOWED" if "Valid" in description else "BLOCKED"
        
        result_ok = (status == expected_status)
        if not result_ok:
            has_failures = True
            
        print(f"Test: {description}")
        print(f"URL: {url_to_test}")
        print(f"Result: {status} (Expected: {expected_status}) -> {'PASS' if result_ok else 'FAIL'}")
        print("-" * 20)
        
    if has_failures:
        print("\nDEMONSTRATION FAILED: One or more checks did not produce the expected outcome.")
        sys.exit(1)
    else:
        print("\nDEMONSTRATION SUCCESSFUL: All checks passed, correctly blocking malicious URLs.")
        sys.exit(0)

Payload

https://modelcontextprotocol.io.attacker-domain.com

Cite this entry

@misc{vaitp:cve202624052,
  title        = {{Insufficient URL validation allows requests to attacker-controlled domains.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-24052},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-24052/}}
}
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 ::