| Название | DataLinkDC dinky (dinky-admin) up to and including released tag v1.2.5 (reviewed at commit ce7678eaa13722541f7d1eeda7ffbf8c9f753e20; v1.2.5 was the newest release at the time of review) Server-Side Request Forgery with interaction/read-back |
|---|
| Описание | dinky's unauthenticated `/api/ws/sql-gateway/` WebSocket dials a caller-chosen `connectAddress` (SSRF: the server's Flink SQL client connects out and the terminal channel returns output to the caller)
Vendor: DataLinkDC
Affected versions: up to and including released tag v1.2.5 (reviewed at commit ce7678eaa13722541f7d1eeda7ffbf8c9f753e20; v1.2.5 was the newest release at the time of review)
Vulnerability class: Server-Side Request Forgery with interaction/read-back (CWE-918)
Preconditions: none — the WebSocket handshake requires no session, cookie or token header. dinky's authentication is a Spring MVC handler interceptor that never executes for a `@ServerEndpoint` WebSocket upgrade, and `SqlGateway.onOpen` performs no token check of its own. Loading the gateway branch of the SQL client needs the Flink client artifacts on the classpath, which the shipped distribution installs through dinky's own Flink dependency setup step in a standard deployment.
Any anonymous network caller can open the WebSocket endpoint `ws://…/api/ws/sql-gateway/` and, through the `connectAddress` query parameter of the upgrade request, choose the host:port that the dinky server process then dials. The value is passed verbatim into the Flink SQL client configuration and `Executor.create` opens a Flink SQL gateway REST connection to the attacker-chosen address; the terminal banner, output and error stacks are pushed back over the WebSocket, giving the caller a read-back channel for the interaction.
Data flow: WS upgrade query `connectAddress` → `SqlGateway.onOpen` (`getParameter("connectAddress", true)`) → `SqlClientOptions.buildConnectAddress` → `SqlClientAdapter` sets Flink `RestOptions.ADDRESS`/`PORT` + `RemoteExecutor` → `FlinkSqlClient.startCli` → `Executor.create(context, inetSocketAddress, sessionId)` dials the attacker-chosen address.
### Affected interfaces
| Interface |
|---|
| WS /api/ws/sql-gateway/?cols=80&rows=24&mode=MODE_GATEWAY&connectAddress=\<host:port\>&initSql=\<sql\> — the `connectAddress` query parameter of the WebSocket upgrade request is fully caller-controlled |
Primary route detail:
```
WS GET /api/ws/sql-gateway/?cols=80&rows=24&mode=MODE_GATEWAY&connectAddress=127.0.0.1:18951&initSql=SELECT%201
^^^^^^^^^^^^^^^^^^^^^^^^^^
the server dials this address, no credential required
```
### Root cause
1. Authentication never executes for this endpoint: sa-token login enforcement is registered as a Spring MVC `HandlerInterceptor` (`AppConfig.java:74-94`), which only runs for MVC handlers — a `javax.websocket` `@ServerEndpoint` upgrade bypasses the MVC dispatch chain entirely, and the only servlet `Filter` in dinky-admin is the CORS filter (`CorsConfig.java:35,49`). The codebase has exactly two `@ServerEndpoint` classes: the global console WebSocket validates a session token on open (`GlobalWebSocket.java:54`, `:79-88`), while `SqlGateway.onOpen` (`SqlGateway.java:142-143`) contains no token or session check anywhere in the file (259 lines).
2. `connectAddress` is read as a required query parameter of the upgrade request (`SqlGateway.java:175`) with no allowlist, no pinning to cluster addresses registered in dinky's own store, and no host/scheme validation.
3. The value flows through `SqlClientOptions.buildConnectAddress` (`SqlClientOptions.java:42-54`) into `SqlClientAdapter.startClient` (`SqlClientAdapter.java:77-94`), which sets Flink `RestOptions.ADDRESS`/`PORT` (`:83-86`) and a `RemoteExecutor` (`:87`); in `MODE_GATEWAY`, `FlinkSqlClient.startCli` calls `Executor.create(context, inetSocketAddress, sessionId)` (`FlinkSqlClient.java:72-80`) — the server process opens the connection to the attacker-chosen address.
4. The channel is bidirectional: terminal output frames are pushed to the WebSocket client (`SqlGateway.java:111-112`), caller keystrokes feed the terminal (`SqlClientAdapter.java:97-104`), and error stacks are also returned (`SqlGateway.java:132-140`).
Missing guard: no authentication on the endpoint and no validation/allowlist for `connectAddress`. This is a different endpoint, handler and client from the raw HTTP proxy servlet under `/api/flink_web/*` (a separate report): that servlet substitutes an HTTP URL template; this is the WebSocket SQL gateway console dialing a Flink SQL gateway endpoint.
Baseline: official dinky v1.2.5 distribution (commit ce7678e…), JDK 11, shipped-default H2 profile, HTTP/WebSocket on `127.0.0.1:18764`; official Flink 1.20 client artifacts added to the classpath of the unmodified distribution (the gateway branch needs them; no dinky code was modified). Canary: `nc -l 127.0.0.1 18951 > ssrf-capture.bin`.
**1. Unauthenticated WebSocket with attacker-chosen `connectAddress`**
```
python3 ws_test.py "ws://127.0.0.1:18764/api/ws/sql-gateway/?cols=80&rows=24&mode=MODE_GATEWAY&connectAddress=127.0.0.1:18951&initSql=SELECT%201"
```
- The WebSocket handshake is accepted with no credential of any kind; the server returns the binary frame `Dinky Sql Client\n`.
- The canary capture file (143 bytes) contains the request dinky itself sent out:
```
GET /v1/api_versions HTTP/1.1
host: 127.0.0.1:18951
connection: close
content-length: 2
content-type: application/json; charset=UTF-8
{}
```
The server log records `Sql Client Start : 127.0.0.1:18951` in the same interval — the outbound connection is dinky's own Flink SQL client dialing the attacker-chosen address.
**2. Negative controls**
- `ws://…/api/ws/global/00000000-invalid-token` — the sibling global WebSocket performs a token check and the server closes the session immediately after the handshake (the protection asymmetry is real).
- The same sql-gateway URL without `connectAddress` — the handshake is still accepted with no credential, but no outbound connection is made and the session is closed (`getParameter("connectAddress", true)` throws, `SqlGateway.java:232`).
- **Vendor contact**: GitHub Private Vulnerability Reporting - DataLinkDC/dinky
- **Status**: vendor notified 2026-10-09 through GitHub Private Vulnerability Reporting; private report https://github.com/DataLinkDC/dinky/security/advisories/GHSA-x4hx-f425-mrx6; not publicly disclosed; no CVE assigned
- **Duplicate check**: public advisories (NVD/OSV/GitHub) and the project issue tracker were searched before reporting; please tell us if this duplicates something already tracked internally. |
|---|
| Источник | ⚠️ https://github.com/DataLinkDC/dinky |
|---|
| Пользователь | lucasg2g (UID 84737) |
|---|
| Представление | 15.10.2026 07:35 (25 дни назад) |
|---|
| Модерация | 08.11.2026 15:35 (24 days later) |
|---|
| Статус | принято |
|---|
| Запись VulDB | 432891 [DataLinkDC Dinky до 1.2.5 sql-gateway Endpoint SqlGateway.java SqlGateway.onOpen connectAddress эскалация привилегий] |
|---|
| Баллы | 20 |
|---|