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.
- CWE
- CWE-918
- 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_urlPayload
../../../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 ::
