Client SSRF / protocol confusion: streamable HTTP transport follows server 3xx into internal+loopback services (follow_redirects=True, no ho

该问题出现在使用 MCP Python SDK 的 Streamable HTTP 客户端连接服务端时,客户端会无条件跟随服务端返回的 3xx 重定向,可能被导向 127.0.0.1 等内网/回环地址;排查时应优先确认 create_mcp_http_client 是否仍设置 follow_redi

快速结论:该问题出现在使用 MCP Python SDK 的 Streamable HTTP 客户端连接服务端时,客户端会无条件跟随服务端返回的 3xx 重定向,可能被导向 127.0.0.1 等内网/回环地址;排查时应优先确认create_mcp_http_client是否仍设置follow_redirects=True,以及是否使用stream_within_origin限制重定向范围。

适用环境:Issue 确认涉及 MCP Python SDK v1 与 v2;报告者验证的版本为 mcp==2.2.0 与 v1.30.0;涉及 Python 标准库 HTTP 服务与 httpx 客户端;未提供操作系统、CUDA、显卡等信息。

最快修复方案:升级到已包含 PR #3397(stream_within_origin,2026-09-04 合入)的版本,使 Streamable HTTP 以follow_redirects=False发送请求,仅跟随同源(同 scheme/host/port,含 http→https 同主机默认端口)重定向。

注意事项:跨端口127.0.0.1不属于同源,因此不会被跟随;若需要跟随跨主机重定向,当前默认策略可能限制既有行为。Issue 中维护者确认mcp==2.2.0与v1.30.0已不可复现,但报告时提出的端到端回归测试(两个回环服务 + Client 断言未跟随、未摄入受害方initialize)尚未合入。

问题场景

用户通过 MCP Python SDK 的 Streamable HTTP 客户端连接 MCP 服务端。当该服务端(或能影响其 HTTP 响应的一方)返回 3xx 重定向时,客户端会跟随跳转,并可能落到内网或回环服务上。若目标恰好是另一个本地 MCP 服务或 JSON-RPC 端点,客户端会把这个内部服务的响应当作原 MCP 服务端的回复摄入到调用方/LLM 会话中,造成 SSRF 探测或协议混淆导致的数据泄露。触发点包括 SSE GET、initialize、工具调用等所有经由该 httpx 客户端发出的请求;302/303(POST→GET、丢弃 body)与 307/308(保留方法与 body)都受影响。

报错原文

Client SSRF / protocol confusion: streamable HTTP transport follows server 3xx into internal+loopback services (follow_redirects=True, no host validation; #2106 closed but fix #2180 never merged)

kwargs: dict[str, Any] = {"follow_redirects": True}   # line 79 — unconditional

Redirect to http://127.0.0.1:<victim> not followed…

原因分析

根本原因在src/mcp/shared/_httpx_utils.py的create_mcp_http_client()中,工厂无条件设置follow_redirects=True,且客户端侧没有任何重定向目标校验(如is_loopback/is_link_local检查)。Streamable HTTP 传输通过该工厂构建 httpx 客户端,因此所有 POST/GET 都会继承该行为。这是服务端 DNS 重绑定防护(transport_security.py)在客户端方向的缺失镜像。

历史修复状态:Issue #2106 曾被标记为已完成,但关联修复 PR #2180 于 2026-08-10 被关闭且merged_at: null,从未合入;报告者在 2026-08-21 检查 main 与已发布的 PyPI wheel 时仍只看到follow_redirects = True。后续 PR #3397 合入后引入stream_within_origin,该问题在 mcp==2.2.0/v1.30.0已不可复现。若使用早期版本,仍可能命中该路径。

环境排查

  • 确认 MCP Python SDK 版本:Issue 验证 mcp==2.2.0 与 v1.30.0 已修复;早期 2.x/1.x 版本可能仍存在follow_redirects=True。
  • 检查src/mcp/shared/_httpx_utils.py的create_mcp_http_client()是否仍无条件设置follow_redirects=True。
  • 确认 Streamable HTTP 传输是否走stream_within_origin(PR #3397),以及是否以follow_redirects=False发送。
  • 确认目标重定向是否跨源:同源判定为同 scheme/host/port,另含 http→https 同主机默认端口;跨端口127.0.0.1视为非同源。
  • 若使用自定义 httpx 客户端工厂或覆写该工厂,需单独检查是否重新引入了无校验的重定向跟随。
  • 复现时可准备两个回环 HTTP 服务,一个返回 307 指向另一个,观察客户端是否摄入受害服务的serverInfo。

解决步骤

  1. 升级 MCP Python SDK 到包含 PR #3397(2026-09-04 合入)的版本,Issue 中维护者验证 mcp==2.2.0 与 v1.30.0 已修复。
  2. 确认升级后 Streamable HTTP 使用stream_within_origin,以follow_redirects=False发送,仅跟随同源重定向。
  3. 复跑原始 PoC 结构:服务端 307 跳到跨端口的 127.0.0.1 受害服务,检查客户端日志是否出现类似Redirect to http://127.0.0.1:<victim> not followed…的提示。
  4. 确认客户端未摄入受害服务的serverInfo(如internal-secret-service)。
  5. 若仍可复现,检查是否使用了自定义客户端工厂或覆写逻辑绕过了stream_within_origin;不要在其上再叠加一层RedirectPolicy。
  6. 可优先尝试补充 Issue 中提出的端到端回归测试:两个回环服务 + Client 断言未跟随重定向、未摄入受害方initialize,以防回归至无校验的 follow-all 行为。

验证方法

在已修复版本上运行原始 PoC 形态:客户端不应跟随跨源(含跨端口 127.0.0.1)重定向,日志应显示重定向未被跟随,且不应把受害服务的serverInfo当作原服务端回复摄入。同时运行tests/shared/test_httpx_utils.py与tests/client/test_streamable_http.py,确认既有的 outside-origin、method-changing、https-downgrade 行为测试通过且无回归。

参考来源

modelcontextprotocol/python-sdk #3358

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29136

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注