Details
### Summary
The `rmcp` library does not validate the `resource` parameter in OAuth Protected Resource metadata (RFC 9728), allowing a malicious MCP server to redirect OAuth flows to a legitimate authorization server and steal the resulting access tokens.
### Details
RFC 9728 specifies two MUST requirements for resource parameter validation:
- Section 7.3: the client MUST ensure that the resource identifier URL it is using as the prefix for the metadata request exactly matches the `resource` value in the returned metadata document.
- Section 3.3: if the `resource` value returned is not identical to the URL the client used, the data MUST NOT be used.
In the current implementation (`crates/rmcp/src/transport/auth.rs`), the `ResourceServerMetadata` struct (lines 390–394) does not include a resource field:
```rust
struct ResourceServerMetadata {
authorization_server: Option<String>,
authorization_servers: Option<Vec<String>>,
scopes_supported: Option<Vec<String>>,
}
```
And discover_oauth_server_via_resource_metadata() (lines 1446–1465) proceeds without any resource URL validation.
#### Recommended fix
1. Add the `resource` field to the struct:
```rust
struct ResourceServerMetadata {
resource: Option<String>, // RFC 9728 REQUIRED field
authorization_server: Option<String>,
authorization_servers: Option<Vec<String>>,
scopes_supported: Option<Vec<String>>,
}
```
2. Add validation logic after fetching metadata:
```rust
let Some(resource_metadata) = self
.fetch_resource_metadata_from_url(&resource_metadata_url)
.await?
else {
return Ok(None);
};
// RFC 9728: validate that the resource identifier matches our target server
if let Some(resource) = &resource_metadata.resource {
if resource.trim_end_matches('/') != self.base_url.as_str().trim_end_matches('/') {
return Err(AuthError::MetadataError(format!(
"Resource metadata mismatch: expected '{}', got '{}'",
self.base_url, resource
)));
}
}
```
### PoC
1. Attacker sets up a malicious MCP server at `fake-mcp.com/mcp`.
2. At `fake-mcp.com/mcp/.well-known/oauth-protected-resource`, the attacker serves metadata declaring:
- resource: `real-mcp.com/mcp` (the legitimate server)
- authorization_servers: the legitimate authorization server(s) of `real-mcp.com/mcp`
3. Victim configures any MCP client using `rmcp` to connect to `fake-mcp.com/mcp`.
4. `rmcp` fetches the protected resource metadata and, without validating that the `resource` field (`real-mcp.com/mcp`) differs from the configured server (`fake-mcp.com/mcp`), initiates an OAuth flow with the legitimate authorization server.
5. The victim sees a legitimate authorization prompt and completes the flow.
6. The resulting access token — valid for `real-mcp.com/mcp` — is sent to `fake-mcp.com/mcp` in subsequent requests.
7. The attacker captures the token and can impersonate the victim on `real-mcp.com/mcp`.
### Impact
This is an access token theft vulnerability via OAuth resource metadata spoofing. All MCP clients built on `rmcp` that rely on OAuth-protected MCP servers are affected. An attacker who tricks a user into connecting to a malicious MCP server can steal valid access tokens for any legitimate MCP server, enabling full impersonation of the victim.
#### Credit
Jian Cui, Minsun Shim, Zhou Li, Xiaojing Liao
University of Illinois Urbana-Champaign (UIUC)
University of California, Irvine (UCI)