← Attack Database

Session hijacking in HTTP MCP

Credential & data verified

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

  1. Client opens a Streamable HTTP session. MCP client sends initialize to the server; server responds with an Mcp-Session-Id header (or equivalent cookie).
  2. Client subscribes to SSE stream. Subsequent requests and server-pushed notifications use the session ID as the routing key.
  3. 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).
  4. 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.
  5. 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.
  6. 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 the initialize source IP and compare against later subscribe/request sources.
  • Session IDs with low entropy. Run ent or equivalent against recently issued IDs; flag if Shannon entropy is implausibly low (the oatpp-mcp pattern).
  • Sequential ID patterns in server logs. A giveaway for pointer-derived or counter-derived IDs.
  • Tokens presented without the expected aud claim 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 Origin header on every connection (anti-DNS-rebinding).
  • Validate the Host header against an expected allowlist when binding to localhost.
  • Enforce RFC 8707 audience binding: MCP servers MUST reject tokens whose aud claim does not name them. MCP clients MUST send the resource parameter 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

  • 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
github.com/policylayer/intercept →
// GET IN TOUCH

Have a question or want to learn more? Send us a message.

Message sent.

We'll get back to you soon.