Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/b68732c140fb11d844b214cf2db50a5a to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/b68732c140fb11d844b214cf2db50a5a to your computer and use it in GitHub Desktop.
AstrBot: Authenticated SSRF in Dashboard MCP connection test via `mcp_server_config.url`

Advisory Details

Title: Authenticated SSRF in Dashboard MCP connection test via mcp_server_config.url

Description:

Summary

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.

Details

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:
    return

The 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/test is the cleanest reproducer.

PoC

Prerequisites

  • 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.

Reproduction Steps

  1. Download the listener used to observe backend callbacks from: listener_server.py
  2. Download the exploit client from: verification_test.py
  3. Download the control script from: control-no_url.py
  4. Download the helper launchers from:
  5. From the AstrBot repository root, install dependencies:
    • uv sync
  6. 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
  7. Run the exploit script:
    • python3 ./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/verification_test.py
  8. The exploit script logs in to /api/auth/login, obtains a JWT, and sends this payload to POST /api/tools/mcp/test:
{
  "mcp_server_config": {
    "url": "http://127.0.0.1:19001/mcp-test",
    "transport": "sse",
    "timeout": 2
  }
}
  1. Observe backend callbacks in verification-callback_observation.log or runtime/callback_observation.log.
  2. Run the control case:
    • python3 ./llm-enhance/cve-finding/ssrf/Issue-astrbot-7171-tools-mcp-test-url-exp/control-no_url.py
  3. Confirm the control run uses the same authenticated endpoint but omits the URL field and produces no listener hits.

Log of Evidence

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: []

Impact

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.

Affected products

  • Ecosystem: pip
  • Package name: AstrBot
  • Affected versions: <= 4.25.2
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

Weaknesses

  • CWE: CWE-918: Server-Side Request Forgery (SSRF)

Occurrences

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment