← All writeups
Medium Bugcrowd · Recorded Future Duplicate

When MCP Meets OAuth, Again: An Open Dynamic Client Registration Endpoint on Recorded Future

oauthdynamic-client-registrationmcpaccount-takeoverauthentication

By Kavennesh Balachandar · Bugcrowd · Recorded Future Managed Bug Bounty Engagement

# TL;DR

mcp.recordedfuture.com exposes an open, unauthenticated OAuth 2.0 Dynamic Client Registration (DCR) endpoint that registers a client with any redirect_uri you supply — external hosts, http://localhost, plain HTTP — and accepts response modes (implicit, token, id_token) that the server's own metadata says it doesn't support. id.recordedfuture.com/authorize then honours those attacker-registered redirect targets. That's a broken-authentication misconfiguration on a core identity provider, and it supplies every precondition for the documented MCP "one-click account takeover." What I did not do is prove the takeover end-to-end — that needs a logged-in victim and consent-screen visibility I didn't have. So this is reported and written up as a Medium / P3 misconfiguration, with the ATO chain described as a documented, unproven potential.

# Background: why redirect_uri is the whole ballgame

In OAuth, after you authenticate, the identity provider sends you back to a redirect_uri carrying an authorization code (or, in the older implicit flow, a token directly in the URL). Whoever controls that callback receives the credential. The entire model rests on one assumption: the IdP only sends the code to a callback the legitimate app registered ahead of time — which is why the OAuth security best practice demands exact-match against a pre-registered allowlist.

Dynamic Client Registration (RFC 7591) lets clients register themselves via an API instead of a human in a dashboard — useful for ecosystems like MCP (Model Context Protocol). But it moves the "who may register a callback" decision into an API, and if that API doesn't restrict what it accepts, anyone can register a callback pointing wherever they like. This class is documented (Obsidian Security, 2025) and tracked in the wild as CVE-2025-4143 (improper redirect_uri validation) and CVE-2025-4144 (PKCE bypass). Recorded Future's MCP gateway reproduces the first half.

# Step 1 — register a client with an attacker callback (no auth)

curl -s -X POST "https://mcp.recordedfuture.com/mcp/register_client" \
  -H "Content-Type: application/json" \
  -d '{"client_name":"poc",
       "redirect_uris":["https://attacker.example/callback","http://localhost:9999/cb"],
       "grant_types":["authorization_code","refresh_token"],
       "response_types":["code"],
       "token_endpoint_auth_method":"none"}'

HTTP 200 — with no Authorization header and no cookie. The response echoes the arbitrary external host and the http://localhost value verbatim, issues a public client (token_endpoint_auth_method: "none" — no secret needed to exchange a code), and hands out a shared mcp-proxy identity with the registrant's IP baked into the client_id. Controls: GET /mcp/register_client401; empty POST400 "redirect_uris must be provided".

# Step 2 — the authorization server honours the attacker's callback

curl -s -i "https://id.recordedfuture.com/authorize?client_id=<registered>&redirect_uri=https://attacker.example/callback&response_type=code&scope=openid%20email%20profile&state=x&code_challenge=<S256>&code_challenge_method=S256"

HTTP 302 → the login flow, pointed at the attacker-registered callback. The negative controls prove this is a registration flaw, not a generic open redirect:

  • Same client, a different unregistered redirect_uri400 "Parameter redirect_uri is invalid".
  • A bogus client_id400 "Parameter client_id is invalid".

So the IdP does validate redirect_uri — against what was registered. The break is that registration is open, so the attacker simply registers the callback they want first.

# Step 3 — the advertised policy isn't enforced

The metadata at /.well-known/oauth-authorization-server states "response_types_supported": ["code"] and "pkce_required": true. In practice, DCR accepts a client whose grant_types include implicit and whose response_types include token / id_token, and authorize?response_type=token returns 302 into the login flow. Implicit responses deliver tokens directly in the URL fragment of the attacker's callback — no code exchange, no PKCE. The one mitigation the metadata promises is bypassable by registering a flow the server said it didn't allow.

# Proven vs. enabled

Proven (unauthenticated, with raw captures): open DCR with arbitrary redirect targets; the authorize endpoint accepting them; the advertised code-only + PKCE policy not enforced; a shared mcp-proxy client identity.

Enabled but NOT proven here (needs an authenticated session I didn't have): the textbook chain — register a client at the attacker's server → send a logged-in victim the crafted /authorize link → if consent is absent/auto-approved/cached for the shared client, the victim's code (or token) lands on the attacker → the attacker exchanges it (public client, no secret) → account access. That is a one-click ATO if and only if the consent step doesn't stop it. I couldn't observe consent behaviour without a platform account, so I don't assert it. The chain was staged (an armed webhook listener) but deliberately never fired — it received zero codes. No real user was ever in the loop.

# Impact and severity

An unauthenticated party can stand up OAuth clients on Recorded Future's IdP whose authorization responses are directed to attacker infrastructure, and can enable response modes the platform says it disallows — removing the redirect_uri integrity guarantee the rest of the OAuth/consent design depends on, on a core identity provider.

Severity as proven: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N → 4.7 (Medium) / P3. Scope is Changed because the misconfigured DCR component subverts redirect integrity on a separate authority (the IdP). If an authenticated consent test later confirms code/token delivery without robust consent, the same issue scores ~9.3 (Critical) / P1–P2. Reported conservatively pending that test.

# Remediation

  • Restrict redirect_uri at DCR to a trusted allowlist (registered domains; loopback only for native clients); reject external and plaintext-HTTP URIs.
  • Enforce the advertised policy — reject implicit/token/id_token registrations and require PKCE.
  • Implement and always re-validate an MCP-layer consent screen showing the client identity and redirect_uri; don't rely on cached upstream consent for the shared mcp-proxy client.
  • Prefer per-client identities over one shared client at the IdP.

# Disclosure timeline

  • 2026-07-01 — Reported via Bugcrowd (Recorded Future Managed Engagement).
  • 2026-07-02 — Triaged as duplicate; under assessment.
  • Pending — authenticated consent test (would drive P1/P2). Public disclosure pending program approval.

# References

  • Obsidian Security — "When MCP Meets OAuth: One-Click Account Takeover" (2025).
  • CVE-2025-4143 — improper redirect_uri validation. CVE-2025-4144 — PKCE bypass.
  • RFC 7591 §5 — DCR security considerations. OAuth 2.0 Security BCPredirect_uri exact-match.