VAITP Dataset

← Back to the dataset

CVE-2026-32871

FastMCP path traversal in OpenAPI params allows authenticated SSRF.

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

FastMCP is a Pythonic way to build MCP servers and clients. Prior to version 3.2.0, the OpenAPIProvider in FastMCP exposes internal APIs to MCP clients by parsing OpenAPI specifications. The RequestDirector class is responsible for constructing HTTP requests to the backend service. A vulnerability exists in the _build_url() method. When an OpenAPI operation defines path parameters (e.g., /api/v1/users/{user_id}), the system directly substitutes parameter values into the URL template string without URL-encoding. Subsequently, urllib.parse.urljoin() resolves the final URL. Since urljoin() interprets ../ sequences as directory traversal, an attacker controlling a path parameter can perform path traversal attacks to escape the intended API prefix and access arbitrary backend endpoints. This results in authenticated SSRF, as requests are sent with the authorization headers configured in the MCP provider. This issue has been patched in version 3.2.0.

CVSS base score
10.0
Published
2026-04-02
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
Unauthorized Access
Affected component
FastMCP
Fixed by upgrading
Yes

Solution

Upgrade FastMCP to version 3.2.0 or later.

Vulnerable code sample

import urllib.parse

# This code represents a simplified version of the vulnerable components
# in FastMCP prior to version 3.2.0, as described in CVE-2026-32871.
# The CVE number is fictional, but the vulnerability pattern is real.

class RequestDirector:
    """
    A simplified representation of the RequestDirector class responsible for
    constructing backend requests.
    """
    def __init__(self, backend_base_url):
        # The base URL of the internal backend service.
        self.backend_base_url = backend_base_url
        # In a real scenario, this would hold authentication tokens for the backend.
        self.auth_headers = {"Authorization": "Bearer server-secret-token"}

    def _build_url(self, path_template, path_params):
        """
        VULNERABLE METHOD: Constructs the final URL to the backend service.
        This method is vulnerable because it does not URL-encode the parameter
        values before substituting them into the path template.
        """
        # VULNERABILITY: Direct string substitution without any sanitization or
        # URL-encoding of the parameters provided by the MCP client.
        substituted_path = path_template.format(**path_params)

        # The `urllib.parse.urljoin` function will then resolve '..' sequences,
        # allowing an attacker to traverse up the URL path from the intended API prefix.
        final_url = urllib.parse.urljoin(self.backend_base_url, substituted_path)
        
        return final_url

    def direct_request(self, path_template, path_params):
        """
        Simulates directing a request from an MCP client to the backend.
        """
        # Build the URL using the vulnerable method.
        target_url = self._build_url(path_template, path_params)
        
        print(f"[*] Simulating authenticated request to backend service...")
        print(f"[*]   Target URL: {target_url}")
        print(f"[*]   Headers: {self.auth_headers}")
        print(f"[*] --- This request would be sent to the backend ---")
        
        return target_url

# --- Demonstration of the Vulnerability ---

if __name__ == '__main__':
    # Configuration of the MCP Provider
    # The internal service that the MCP server communicates with.
    BACKEND_SERVICE_URL = "http://internal-backend-service:8080/"
    
    # The OpenAPI operation path template.
    API_PATH_TEMPLATE = "/api/v1/users/{user_id}"

    # Instantiate the director
    director = RequestDirector(BACKEND_SERVICE_URL)

    # --- 1. Legitimate Use Case ---
    print("--- Legitimate Use Case ---")
    legitimate_params = {'user_id': 'user-abc-123'}
    director.direct_request(API_PATH_TEMPLATE, legitimate_params)
    # Expected safe URL: http://internal-backend-service:8080/api/v1/users/user-abc-123
    
    print("\n" + "="*40 + "\n")

    # --- 2. Attack Scenario (Path Traversal) ---
    print("--- Attack Scenario (Path Traversal) ---")
    # The attacker controls the 'user_id' parameter.
    # The payload uses '..' to traverse up from '/api/v1/users/'
    # and access an unintended internal endpoint.
    # The path is /api/v1/users/{payload}. We need to go up 3 levels to escape.
    attack_payload = "../../../internal/monitoring/health"
    attack_params = {'user_id': attack_payload}
    
    director.direct_request(API_PATH_TEMPLATE, attack_params)
    # The vulnerability causes the final URL to be:
    # http://internal-backend-service:8080/internal/monitoring/health
    # This allows the attacker to bypass the intended '/api/v1/' scope
    # and access arbitrary endpoints on the backend service with the
    # server's authentication credentials (Authenticated SSRF).

Patched code sample

import urllib.parse
from typing import Dict, Any

class RequestDirector:
    """
    A corrected version of the RequestDirector class where path parameters
    are URL-encoded to prevent path traversal.
    """
    def _build_url(self, base_url: str, path_template: str, path_params: Dict[str, Any]) -> str:
        """
        Constructs the final request URL by safely substituting path parameters.

        This method fixes the path traversal vulnerability by URL-encoding each
        parameter value before substituting it into the path template. The `safe=''`
        argument to `urllib.parse.quote` ensures that characters like '/' are also
        encoded, preventing them from being interpreted as path separators.
        """
        # Create a new dictionary with URL-encoded values for path parameters.
        # The `str()` cast handles non-string parameter types.
        encoded_params = {
            key: urllib.parse.quote(str(value), safe='')
            for key, value in path_params.items()
        }

        # Substitute the sanitized, URL-encoded values into the template.
        path = path_template.format(**encoded_params)

        # `urljoin` can now safely combine the base URL and the constructed path,
        # as any traversal sequences like '../' have been neutralized by encoding.
        final_url = urllib.parse.urljoin(base_url, path)

        return final_url

Payload

../../../internal/admin/dashboard

Cite this entry

@misc{vaitp:cve202632871,
  title        = {{FastMCP path traversal in OpenAPI params allows authenticated SSRF.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-32871},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-32871/}}
}
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 ::