# Root Cause Analysis: CVE-2026-34742

## Summary

CVE-2026-34742 is a DNS Rebinding vulnerability in the Go MCP SDK versions prior to 1.4.0. The Model Context Protocol (MCP) Go SDK's StreamableHTTPHandler does not enable DNS rebinding protection by default for HTTP-based servers. When an HTTP-based MCP server runs on localhost without authentication, a malicious website can exploit DNS rebinding to bypass same-origin policy restrictions and send requests to the local MCP server. This allows attackers to invoke tools or access resources exposed by the MCP server on behalf of the user, potentially leading to SSRF, credential theft, and internal network access.

## Impact

**Package/Component Affected:**
- `github.com/modelcontextprotocol/go-sdk/mcp` - StreamableHTTPHandler and SSEHandler

**Affected Versions:**
- All versions < 1.4.0 are vulnerable
- Fixed in version 1.4.0

**Risk Level:**
- CVSS Score: 8.1 (High)
- CWE-1188: Initialization of a Resource with an Insecure Default

**Consequences:**
- Server-Side Request Forgery (SSRF) against localhost services
- Bypass of same-origin policy (SOP) restrictions
- Unauthorized access to tools and resources exposed by MCP servers
- Potential credential theft and internal network access
- Attackers can invoke MCP tools that may expose sensitive data or perform privileged operations

## Root Cause

The Go MCP SDK v1.3.0's `StreamableHTTPHandler.ServeHTTP()` method does not validate the HTTP `Host` header against the server's local address. When a server is listening on a loopback address (127.0.0.1, [::1], localhost), the handler should verify that incoming requests have a matching loopback `Host` header to prevent DNS rebinding attacks.

The vulnerable code in `mcp/streamable.go` (v1.3.0) immediately processes requests without Host validation:

```go
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // No DNS rebinding protection in v1.3.0
    // Allow multiple 'Accept' headers...
    accept := strings.Split(strings.Join(req.Header.Values("Accept"), ","), ",")
    // ... rest of handler
}
```

The fix in v1.4.0 adds automatic DNS rebinding protection:

```go
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    // DNS rebinding protection: auto-enabled for localhost servers.
    if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
        if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
            if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
                http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
                return
            }
        }
    }
    // ... rest of handler
}
```

The fix adds a `DisableLocalhostProtection` option to `StreamableHTTPOptions` (default: false, meaning protection is enabled by default).

**Fix Commit:**
- https://github.com/modelcontextprotocol/go-sdk/releases/tag/v1.4.0
- GitHub Security Advisory: GHSA-xw59-hvm2-8pj6

## Reproduction Steps

See `repro/reproduction_steps.sh` for the full reproduction script.

**What the script does:**
1. Clones the vulnerable Go MCP SDK (v1.3.0)
2. Builds a test MCP server with a sensitive tool (`get_secret_data`) using StreamableHTTPHandler
3. Starts the server on 127.0.0.1:18080 (localhost)
4. Sends HTTP requests to the server with:
   - Legitimate Host header: `127.0.0.1:18080`
   - Malicious Host headers: `attacker-controlled.com`, `evil.com`
5. Attempts to list tools and execute the sensitive tool via malicious Host headers

**Expected Evidence of Reproduction:**
- Server returns HTTP 200 for both legitimate and malicious Host headers
- Server exposes tool list to requests with `Host: attacker-controlled.com`
- Server executes sensitive tool via `Host: evil.com` and returns `SECRET_DATA_EXPOSED: api_key=sk-test-12345-localhost-only`
- This proves the server does NOT validate Host headers against local address

**Actual Results:**
```
Test 1 (localhost): HTTP 200 - tool list returned
Test 2 (attacker.com): HTTP 200 - tool list returned  
Test 3 (evil.com): HTTP 200 - SECRET_DATA_EXPOSED returned
```

## Evidence

**Log File Locations:**
- `logs/server.log` - MCP server startup logs
- `logs/build.log` - Build output
- `artifacts/response_localhost.json` - Response from legitimate localhost request
- `artifacts/response_attacker.json` - Response from malicious Host request
- `artifacts/response_tool_call.json` - Response from sensitive tool execution via malicious Host
- `artifacts/status_localhost.txt` - HTTP status for localhost request (200)
- `artifacts/status_attacker.txt` - HTTP status for malicious request (200)
- `artifacts/status_tool_call.txt` - HTTP status for tool call via malicious Host (200)
- `artifacts/request_attacker.txt` - Request details showing malicious Host header

**Key Evidence Excerpts:**

Server accepted request with malicious Host header:
```
HTTP Status: 200
Response: event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"description":"Returns sensitive internal data","name":"get_secret_data"}]}}
```

Secret data exposed via malicious Host:
```
HTTP Status: 200  
Response: event: message
data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"SECRET_DATA_EXPOSED: api_key=sk-test-12345-localhost-only"}]}}
```

**Environment Details:**
- Go version: 1.23.1
- SDK version: v1.3.0 (vulnerable)
- Server address: 127.0.0.1:18080
- Runtime: Linux ARM64

## Recommendations / Next Steps

**Immediate Fix:**
Upgrade to `github.com/modelcontextprotocol/go-sdk` v1.4.0 or later, which includes automatic DNS rebinding protection.

```go
// go.mod
require github.com/modelcontextprotocol/go-sdk v1.4.0
```

**Testing Recommendations:**
1. Verify that servers on localhost reject requests with non-localhost Host headers (403 Forbidden)
2. Test with various malicious Host headers including:
   - attacker-controlled.com
   - rebinding attacks via dynamic DNS
3. Ensure legitimate localhost requests still work correctly

**Security Best Practices:**
1. Do NOT disable DNS rebinding protection (`DisableLocalhostProtection: false` should remain default)
2. Run HTTP-based MCP servers with authentication when possible
3. Use stdio transport for local-only servers (not affected by this vulnerability)
4. Monitor for suspicious requests with mismatched Host headers

**Workaround (if upgrade not possible):**
Implement a middleware that validates the Host header before passing to the MCP handler:

```go
func hostValidationMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
            if isLoopback(localAddr.String()) && !isLoopback(req.Host) {
                http.Error(w, "Forbidden: invalid Host header", http.StatusForbidden)
                return
            }
        }
        next.ServeHTTP(w, req)
    })
}
```

## Additional Notes

**Idempotency Confirmation:**
The reproduction script has been run multiple times with consistent results:
- Each run confirms the vulnerability exists in v1.3.0
- All malicious Host headers are accepted (HTTP 200)
- Secret data is exposed on each successful reproduction

**Edge Cases and Limitations:**
- The vulnerability primarily affects HTTP-based servers running on localhost (127.0.0.1, [::1])
- Servers running on 0.0.0.0 or non-loopback interfaces are less affected
- The stdio transport is not affected by this vulnerability
- Browsers are the primary attack vector due to same-origin policy enforcement
- Attack requires attacker to control DNS (or manipulate /etc/hosts for testing)

**Scope Note:**
This reproduction demonstrates the library-level vulnerability via a test harness. A production reproduction would require browser-based exploitation or actual DNS rebinding infrastructure. The core vulnerability (lack of Host header validation) is confirmed through this library-level test.
