Session hijacking in HTTP MCP
Session hijacking in HTTP MCP
Summary
MCP supports an HTTP + Server-Sent Events transport (“Streamable HTTP”) for remote servers. Every session is identified by an Mcp-Session-Id header. If that identifier is predictable, guessable, or not bound to the authenticated principal, an attacker with network access can attach to another user’s SSE stream and both read the victim’s tool I/O and inject tool calls into the model’s context. The attack class is parallel to classical web session fixation / session hijacking but in the agent tier. Two CVEs have been disclosed against named MCP SDK implementations, and the MCP 2025-11-25 specification now mandates audience-bound tokens (RFC 8707) and secure session-ID generation to close the general class.
How it works
- Client opens a Streamable HTTP session. MCP client sends
initializeto the server; server responds with anMcp-Session-Idheader (or equivalent cookie). - Client subscribes to SSE stream. Subsequent requests and server-pushed notifications use the session ID as the routing key.
- Attacker obtains a valid session ID. Three known vectors:
- Predictable IDs — the server uses an instance pointer, sequence counter, or low-entropy value.
oatpp-mcp(CVE-2025-6515) returned the C++Session*pointer as the ID; after memory recycling, attackers could predict IDs returned to new clients. - Replay — the server does not bind the session ID to the authenticated principal, so a session ID captured from logs, proxy traces, or a leaked URL works from any IP. The MCP Ruby SDK (CVE-2026-33946) had this flaw.
- DNS rebinding against localhost MCP — a malicious webpage uses DNS rebinding to reach an unauthenticated localhost MCP server and steal or create sessions. MCP TypeScript SDK shipped without DNS rebinding protection by default (CVE-2025-66414); the Python SDK was patched in 1.23.0; Java SDK had a parallel issue (CVE-2026-35568).
- Predictable IDs — the server uses an instance pointer, sequence counter, or low-entropy value.
- Attacker attaches to the SSE stream using the stolen session ID. The server pushes every subsequent server-initiated message (tool-call results, sampling requests, notifications) to both legitimate client and attacker.
- Attacker injects tool calls or responses. Depending on the server’s binding rules, the attacker may send tool-call results that the agent treats as trusted — a “prompt hijacking” payload.
- Token replay amplifies this. If the MCP server does not validate the token’s
aud(audience) claim against itself — the pre-RFC 8707 pattern — an attacker who steals a session-bound bearer token can replay it against a different MCP server or downstream API. The MCP spec now mandates RFC 8707 to close this “confused deputy” variant.
Real-world examples
CVE-2025-6515 — oatpp-mcp predictable session IDs (Oct 2025, CVSS 6.8)
JFrog Security Research disclosed that oatpp-mcp (a Model Context Protocol implementation on Oat++) returned the in-memory address of the Session object as the session ID via Session::getId(). Because glibc malloc recycles freed addresses, an attacker creating and destroying sessions in a loop could enumerate and then predict the IDs that would be reassigned to legitimate clients. Exploitation required network access to the SSE endpoint. JFrog published a proof-of-concept attaching to and hijacking a victim SSE stream. (JFrog blog; JFrog advisory JFSA-2025-001494691; NVD CVE-2025-6515; The Register, 21 Oct 2025)
CVE-2026-33946 — MCP Ruby SDK SSE stream hijacking via session ID replay (2026)
The Ruby SDK’s streamable_http_transport.rb failed to bind session IDs to the authenticated client. Anyone who obtained a valid session ID — from a log file, a proxy trace, a leaked debug dump — could attach to the victim’s SSE stream and intercept all real-time data. Fixed in 0.9.2. (GitLab Advisory; SentinelOne CVE page; RubySec)
CVE-2025-66414 — MCP TypeScript SDK DNS rebinding by default (Dec 2025, CVSS 7.6)
The TypeScript SDK shipped StreamableHTTPServerTransport and SSEServerTransport with DNS rebinding protection disabled by default. A malicious website could use DNS rebinding to bypass same-origin policy and reach a localhost MCP server, invoking its tools with the user’s authority. Fixed in 1.24.0, which enables protection by default when binding to localhost. (GitHub Advisory GHSA-w48q-cv73-mx4w; GitLab Advisory; Vulnerable MCP Project)
CVE-2026-35568 — MCP Java SDK DNS rebinding (2026)
Parallel DNS rebinding vulnerability in the MCP Java SDK io.modelcontextprotocol.sdk:mcp-core. (GitLab Advisory)
MCP Python SDK — DNS rebinding fix in 1.23.0
IBM’s watsonx.data security bulletin documents the fix in the Python SDK at version 1.23.0 for the same DNS rebinding class. (IBM Security Bulletin)
Impact
- Full agent session takeover. Attacker sees every tool call and every tool result, which often contains the data the agent was retrieving (customer records, source files, emails).
- Prompt injection with impersonated tool identity. Attacker sends forged tool results to the victim’s agent; the model has no way to distinguish them from real server output. Classified as “prompt hijacking” in the JFrog disclosure.
- Lateral movement via replayed tokens. Without RFC 8707 audience binding, a bearer token from one MCP server can be replayed against another (the confused deputy problem the MCP spec calls out explicitly).
- No client-side indicator. The victim agent continues to function normally. Detection typically requires server-side anomaly monitoring.
Detection
- Two active clients on the same session ID. The MCP spec allows session resumption but not concurrent subscription; detect and alert.
- SSE subscription from an IP that did not perform the
initialize. Strong hijack signal. MCP servers should log theinitializesource IP and compare against later subscribe/request sources. - Session IDs with low entropy. Run
entor equivalent against recently issued IDs; flag if Shannon entropy is implausibly low (theoatpp-mcppattern). - Sequential ID patterns in server logs. A giveaway for pointer-derived or counter-derived IDs.
- Tokens presented without the expected
audclaim pointing at this MCP server — block and log (MCP 2025-11-25 mandates this). - Localhost MCP servers answering requests with
Host: <unexpected>— the DNS rebinding fingerprint.
Prevention
At the MCP server — baseline required by spec:
- Generate session IDs with a CSPRNG, 128+ bits of entropy (UUIDv4 or better). The MCP 2025-11-25 security best practices document is explicit.
- Bind the session ID to the authenticated principal: the spec recommends
<user_id>:<session_id>and verifying both halves on every inbound request. - Validate the
Originheader on every connection (anti-DNS-rebinding). - Validate the
Hostheader against an expected allowlist when binding to localhost. - Enforce RFC 8707 audience binding: MCP servers MUST reject tokens whose
audclaim does not name them. MCP clients MUST send theresourceparameter on every authorisation and token request. - Short-lived access tokens and rotated refresh tokens, per OAuth 2.1 §7.1.
- Require TLS for all remote transports; prohibit access tokens in URL query strings.
Source: MCP 2025-11-25 Authorization spec and Security Best Practices.
At the transport layer (Intercept):
Intercept terminates the MCP session itself, so it is the natural place to enforce session-ID generation, principal binding, and audience validation independently of whatever SDK the upstream MCP server uses.
# Speculative example — session-ID and audience enforcement applied at the
# proxy. Illustrative; tune for your deployment.
version: "1"
description: "Harden MCP session security at the gateway"
default: "allow"
transport:
session:
id_entropy_bits: 128 # reject server-issued IDs with less
bind_to_principal: true # <user_id>:<session_id>
max_idle: 15m
single_subscriber: true # reject concurrent SSE attaches
origin_allowlist:
- "https://app.example.com"
token:
require_audience: "https://mcp.example.com/mcp" # RFC 8707
reject_query_string_tokens: true
require_https: true
tools:
"*":
rules:
- name: "global rate limit"
rate_limit: 60/minute
Non-policy controls:
- Pin MCP SDK versions to the fixed releases: TS ≥ 1.24.0, Python ≥ 1.23.0, Ruby ≥ 0.9.2, Java patched against CVE-2026-35568.
- Never run HTTP-based MCP servers on localhost without authentication — MCP SDK maintainers and the spec both state this explicitly.
- Separate authorisation server per MCP resource, so even a stolen token can only hit one server.
Sources
- MCP 2025-11-25 Authorization specification
- MCP Security Best Practices (draft)
- RFC 8707 — Resource Indicators for OAuth 2.0
- JFrog — “CVE-2025-6515 Prompt Hijacking Attack”
- JFrog Security Research — oatpp-mcp prompt hijacking advisory
- NVD — CVE-2025-6515
- The Register — “MCP attack uses predictable session IDs to hijack AI agents” (21 Oct 2025)
- GitHub Advisory GHSA-w48q-cv73-mx4w — CVE-2025-66414 (TypeScript SDK)
- GitLab Advisory — CVE-2026-33946 (Ruby SDK)
- GitLab Advisory — CVE-2026-35568 (Java SDK)
- IBM Security Bulletin — MCP Python SDK DNS Rebinding (Fixed in 1.23.0)
- Rafter — “DNS Rebinding and Localhost MCP”
- Vulnerable MCP Project — DNS Rebinding catalogue
Related attacks
- Credential leak via error messages
- Data exfiltration via tool chaining
- Confused deputy / token passthrough
Protect your agent in 30 seconds
Scans your MCP config and generates enforcement policies for every server.
npx -y @policylayer/intercept init