Title: Authenticated SSRF in Dashboard MCP connection test via mcp_server_config.url
Description:
AstrBot exposes an authenticated server-side request forgery issue in its Dashboard MCP connection test workflow. Any authenticated Dashboard user who can call POST /api/tools/mcp/test can supply a crafted mcp_server_config.url value and make the AstrBot server send outbound HTTP requests to attacker-chosen destinations reachable from the host, including loopback and internal network targets.
The vulnerable feature is the Dashboard MCP server connection tester.
ToolsRoute.test_mcp_connection() accepts attacker-controlled JSON from POST /api/tools/mcp/test, extracts mcp_server_config, and forwards URL-based configurations into the MCP connection test path:
server_data = await request.json
config = server_data.get("mcp_server_config", None)
...
validate_mcp_stdio_config(config)
tools_name = await self.tool_mgr.test_mcp_server_connection(config)For URL-based configurations, validate_mcp_stdio_config() is not an SSRF defense. It explicitly returns immediately when url is present:
cfg = _prepare_config(config.copy())
if "url" in cfg:
returnThe actual outbound request is made inside _quick_test_mcp_connection() in the provider layer. The function reads cfg["url"] and directly issues an HTTP request with aiohttp:
url = cfg["url"]
...
async with session.post(url, ...) as response:
...
...
async with session.get(url, ...) as response:
...The manager entry point used by the Dashboard route confirms that any config containing url reaches this sink before MCP session setup completes:
if "url" in config:
success, error_msg = await _quick_test_mcp_connection(config)Observed properties of the vulnerable flow:
- The attacker controls
mcp_server_config.url. - No SSRF-specific validation was found for scheme, hostname, resolved IP, loopback, RFC1918/private ranges, link-local targets, metadata endpoints, or redirect destinations before the request is sent.
- The issue is exploitable even when the MCP handshake fails later, because the backend request is already issued during the preliminary connection test.
- The same underlying connection test path is also reachable from MCP add/update flows, but
POST /api/tools/mcp/testis the cleanest reproducer.
- AstrBot Dashboard must be enabled and reachable.
- A valid Dashboard account or JWT/session is required.
- The AstrBot host must be able to reach the attacker-selected destination.
- Python/uv dependencies for the local lab must be installed.
- Download the listener used to observe backend callbacks from: listener_server.py
- Download the exploit client from: verification_test.py
- Download the control script from: control-no_url.py
- Download the helper launchers from:
- From the AstrBot repository root, install dependencies:
uv sync
- Start the observer and target service:
./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/start_listener.sh./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/start_target.sh
- Run the exploit script:
python3 ./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/verification_test.py
- The exploit script logs in to
/api/auth/login, obtains a JWT, and sends this payload toPOST /api/tools/mcp/test:
{
"mcp_server_config": {
"url": "http://127.0.0.1:19001/mcp-test",
"transport": "sse",
"timeout": 2
}
}- Observe backend callbacks in
verification-callback_observation.logorruntime/callback_observation.log. - Run the control case:
python3 ./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/control-no_url.py
- Confirm the control run uses the same authenticated endpoint but omits the URL field and produces no listener hits.
Exploit run (verification-observation.log):
Verification mode: End-to-End
Endpoint: POST /api/tools/mcp/test
HTTP status: 200
Response body: {"status":"error","message":"Failed to test MCP connection: unhandled errors in a TaskGroup (1 sub-exception)","data":null}
Listener hits: ['GET /mcp-test', 'GET /mcp-test', 'GET /mcp-test']
Independent callback evidence (verification-callback_observation.log):
2026-05-30T17:35:21.441301+00:00 GET /mcp-test
2026-05-30T17:35:21.446947+00:00 GET /mcp-test
2026-05-30T17:35:21.465805+00:00 GET /mcp-test
Control run (control-observation.log):
Verification mode: End-to-End
Endpoint: POST /api/tools/mcp/test
HTTP status: 200
Response body: {"status":"error","message":"Failed to test MCP connection: Connection closed","data":null}
Listener hits: []
This is an authenticated SSRF affecting the AstrBot Dashboard MCP testing workflow. Any authenticated Dashboard user can coerce the server into making outbound requests to arbitrary HTTP endpoints reachable from the AstrBot host. In real deployments, this can be used for loopback probing, internal service discovery, access attempts against internal-only HTTP services, and metadata/service identity probing where network policy allows it.
- Ecosystem: pip
- Package name: AstrBot
- Affected versions: <= 4.25.2
- Patched versions:
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
- CWE: CWE-918: Server-Side Request Forgery (SSRF)
| Permalink | Description |
|---|---|
| https://github.com/AstrBotDevs/AstrBot/blob/0e973bd4d483d18e1672c4dfa2eb7aae31bc1f83/astrbot/dashboard/routes/tools.py#L392-L433 | POST /api/tools/mcp/test accepts attacker-controlled mcp_server_config, performs only stdio-oriented validation, and forwards URL-based configs into the MCP connection test path. |
| https://github.com/AstrBotDevs/AstrBot/blob/0e973bd4d483d18e1672c4dfa2eb7aae31bc1f83/astrbot/core/agent/mcp_client.py#L204-L208 | validate_mcp_stdio_config() explicitly returns when url is present, so URL-based MCP configurations bypass this validation path entirely. |
| https://github.com/AstrBotDevs/AstrBot/blob/0e973bd4d483d18e1672c4dfa2eb7aae31bc1f83/astrbot/core/provider/func_tool_manager.py#L158-L210 | _quick_test_mcp_connection() reads attacker-controlled cfg["url"] and performs outbound aiohttp POST/GET requests without SSRF-oriented destination validation. |
| https://github.com/AstrBotDevs/AstrBot/blob/0e973bd4d483d18e1672c4dfa2eb7aae31bc1f83/astrbot/core/provider/func_tool_manager.py#L713-L723 | test_mcp_server_connection() is the Dashboard-reachable manager entry point that invokes _quick_test_mcp_connection() whenever a config contains url. |