Budibase Unauthenticated NoSQL Operator Injection (CVE-2026-54350)
Published: 2026-07-27 • Researcher: BiiTts (Caio Fabrício)
- Severity
- Critical
- CVE
- CVE-2026-54350 (GHSA-8qv3-p479-cj62)
- Category
- web
- Affected product
- Budibase (open-source low-code application platform) — POST /api/v2/queries/:queryId
- Affected versions
- Vendor advisory / NVD range < 3.39.12; empirically the bare-quote injection vector used by this PoC is only exploitable on <= 3.39.0 — from 3.39.1 the templating layer already escapes injected quotes, and the explicit fix (processJsonStringSync) lands in 3.39.9
- Disclosed
- 2026-07-27
- Patch status
- Patched
References
Archive entry
intelseclab/poc-archiveOn this page
Metadata
| Field | Value |
|---|---|
| Date Added | 2026-07-27 |
| Last Updated | 2026-07-27 |
| Author / Researcher | BiiTts (Caio Fabrício) |
| CVE / Advisory | CVE-2026-54350 (GHSA-8qv3-p479-cj62) |
| Category | web |
| Severity | Critical |
| CVSS Score | 10.0 (CVSS 3.1, AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N) |
| Status | Weaponized — reproduced end-to-end against a real budibase/budibase:3.39.0 instance |
| Tags | budibase, nosql-injection, mongodb, unauthenticated, low-code, json-injection |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Budibase (open-source low-code application platform) — POST /api/v2/queries/:queryId |
| Versions Affected | Vendor advisory / NVD range < 3.39.12; empirically the bare-quote injection vector used by this PoC is only exploitable on <= 3.39.0 — from 3.39.1 the templating layer already escapes injected quotes, and the explicit fix (processJsonStringSync) lands in 3.39.9 |
| Language / Platform | Node.js / TypeScript backend, MongoDB (and other document datasources: CouchDB, Elasticsearch, DynamoDB-PartiQL, REST-with-JSON-body) |
| Authentication Required | No |
| Network Access Required | Yes — direct HTTP(S) reachability to a Budibase instance that has at least one published app with a PUBLIC-role query |
Summary
Budibase queries interpolate user-supplied parameters directly into a query’s raw JSON body via Handlebars, then JSON.parse the result. The only input filter blocks Handlebars markers ({{/}}) but does not block ", \, } or $ — so a parameter value containing a closing quote breaks out of its intended string slot and injects a sibling JSON key. Supplying a duplicate key whose value is a MongoDB operator object (e.g. {"$exists":true}) wins the JSON.parse duplicate-key merge and is passed straight into collection.find() or collection.updateMany(). Because any query whose access role is set to PUBLIC bypasses both session authentication and CSRF checks in the authorized() middleware, and the x-budibase-app-id header needed to reach it is public (embedded in every published-app URL), an unauthenticated remote attacker can dump or mass-modify every document in the backing collection with a single HTTP request. This is NoSQL operator injection (CWE-943 / CWE-89-adjacent), not remote code execution — some summaries of this CVE frame it as RCE, which is incorrect; the confirmed impact is unauthenticated bulk read/write of database contents, not arbitrary code execution on the server.
Vulnerability Details
Root Cause
enrichContext() (packages/server/src/sdk/workspace/queries/queries.ts) builds the outgoing query body by running the builder’s raw JSON template (e.g. {"name":"{{ name }}"}) through Handlebars’ processStringSync() with noEscaping: true, then JSON.parses the resulting string. On vulnerable builds this applies uniformly to every string field, including the JSON body itself — so parameter values are interpolated with no JSON escaping. The only guard, validateQueryInputs() (api/controllers/query/index.ts), rejects a value only if it contains a literal Handlebars block ({{/}}); it does not sanitize ", \, } or $. A parameter value that closes the intended string with " and appends a duplicate JSON key survives validation, gets interpolated raw, and — thanks to JSON.parse’s “last key wins” duplicate-key semantics — silently overrides the original key with an attacker-controlled value, including a MongoDB query operator object. That parsed object is passed unmodified into collection.find() / collection.updateMany() in integrations/mongodb.ts.
Access control does not save this: authorized() (middleware/authorized.ts) short-circuits with return next() whenever the query’s resolved resource role includes the built-in PUBLIC role, skipping session auth and CSRF entirely. Marking a query PUBLIC is a first-class, intended builder feature (it’s how published apps expose data to anonymous visitors in the first place) — the bug is that the JSON body underneath that public-facing query can be reshaped by the attacker, not that PUBLIC queries exist.
Attack Vector
- Identify a published Budibase app with a query whose access role is
PUBLICand whose JSON body embeds a parameter inside a string value (e.g.{"name":"{{ name }}"}), backed by MongoDB or another document datasource. - Obtain the app’s
app_...id (public, embedded in the published app’s URL/traffic) and the target query’squery_...id (visible in the app’s own client-side API calls). - Send an unauthenticated
POST /api/v2/queries/:queryIdwith headerx-budibase-app-id: <app_id>and aparametersbody where the targeted field’s value is:Outputzzz","name":{"$exists":true},"$comment":"cve-2026-54350 - The server interpolates this raw into the JSON template, producing
{"name":"zzz","name":{"$exists":true},"$comment":"cve-2026-54350"};JSON.parsekeeps the lastnamekey, yielding{"name":{"$exists":true},"$comment":"..."}— a filter that matches every document.$commentis an inert MongoDB meta-operator used purely to absorb the template’s trailing"}so the body stays valid JSON. - Against a
find-type query this dumps the whole collection; against anupdateMany-type query with the same technique, the filter is widened to match every document, so the query’s$setwrite applies to the entire collection.
Impact
- Confidentiality: unauthenticated bulk read of every document in the backing collection of any PUBLIC read query, including any sensitive fields the app was never meant to expose collection-wide.
- Integrity: unauthenticated bulk modification of every document in the backing collection when a PUBLIC write (update) query exists.
- Single unauthenticated HTTP request, no session, no CSRF token, cross-origin capable (CVSS
S:C— scope change into the underlying datastore). - Not remote code execution. There is no code-execution primitive in this chain: the injected value only reaches a MongoDB query-operator context (
collection.find()/collection.updateMany()), not an interpreter,eval, template-engine sandbox escape, or OS command. Impact is confined to the contents of the backing document datastore.
Environment / Lab Setup
Target: budibase/budibase:3.39.0 (Docker) + mongo:4.4 (Docker), --network host
(mongo 4.4 chosen because the test host lacks AVX, required by Mongo 5.0+;
3.39.0 chosen because 3.39.1+ already escapes the injected quotes)
Attacker: Python 3, standard library only (urllib) — no third-party dependencies
Tools: exploit.py (this folder), lab/provision.py + lab/setup.sh (this folder)Setup Steps
| |
Proof of Concept
See
exploit.py(full, unmodified) andupstream-README.md/ANALYSIS.mdin this folder — mirrored from BiiTts/CVE-2026-54350-Budibase-NoSQL-Injection. Verified before ingestion:exploit.pyis a self-contained, stdlib-only (urllib) script that sends a genuine crafted HTTP request to the real vulnerable endpoint (POST /api/v2/queries/:queryIdwithx-budibase-app-id) and parses the real JSON response — no obfuscation, no unrelated network calls, no destructive default behavior.lab/provision.pyandlab/setup.shbuild a legitimate, reproducible Docker lab against the real upstreambudibase/budibase:3.39.0image andmongo:4.4, provisioning the PUBLIC query through Budibase’s own builder REST APIs rather than hand-editing internal state.ANALYSIS.mdwalks the exact vulnerable source paths (enrichContext,validateQueryInputs,authorized.ts,integrations/mongodb.ts) across the vulnerable (3.39.0) and fixed (3.39.9+) tags, with a documented empirical version boundary. This is consistent with genuine, tested exploitation rather than a template or guess.
Step-by-Step Reproduction
Stand up the lab — Docker containers + provisioned PUBLIC queries
Shell script1 2 3 4 5bash lab/setup.sh # prints, e.g.: # PROD_APP_ID : app_... # READ_QUERY : ... # UPDATE_QUERY: ...Dump the entire collection via the PUBLIC read query — no auth required
Shell script1 2python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \ --query-id <READ_QUERY> --mode readMass-modify the entire collection via the PUBLIC update query — no auth required
Shell script1 2python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \ --query-id <UPDATE_QUERY> --mode write
Exploit Code
| |
Expected Output
[+] Dumped 5 document(s) unauthenticated:
[
{
"name": "Alice Nguyen",
"email": "alice@example.com",
"ssn": "REDACTED-001",
"plan": "pro",
"balance": 1240.55
},
...
]Screenshots / Evidence
- Not included in this archive entry. The upstream author’s README references a captured run in
EVIDENCE.txt, which is not committed to the source repository (git history shows onlyexploit.py,ANALYSIS.md,README.md, andlab/); reproduce locally vialab/setup.sh+exploit.pyto generate equivalent evidence.
Detection & Indicators of Compromise
SIEM / IDS Rule (example):
alert http any any -> any any (msg:"Possible Budibase NoSQL operator injection (CVE-2026-54350)"; content:"/api/v2/queries/"; http_uri; content:"$exists"; http_client_body; sid:9000002;)Remediation
| Action | Detail |
|---|---|
| Patch | Upgrade to Budibase >= 3.39.12 per the vendor advisory (GHSA-8qv3-p479-cj62); at minimum >= 3.39.9, where processJsonStringSync JSON-escapes interpolated parameters before JSON.parse. |
| Workaround | Audit all published apps for queries whose access role is PUBLIC; restrict them to authenticated roles wherever anonymous access is not a deliberate design requirement. |
| Config Hardening | For document datasources, prefer parameterized queries / driver-level query builders over string-templated JSON bodies wherever the Budibase version and datasource support it. |
References
Notes
Impact framing correction: some descriptions of CVE-2026-54350 circulating alongside its CVSS 10.0 score imply remote code execution. That framing is not supported by the vendor advisory, the NVD vector string (.../A:N, i.e. no availability impact and no code-execution component), or the code-level analysis in ANALYSIS.md. The actual, verified primitive is NoSQL operator injection — unauthenticated bulk read and bulk write of MongoDB (or other document datastore) contents via query-operator injection through a JSON-templating flaw. It is analogous in spirit to classic SQL injection reaching a query builder, not to an RCE chain. The CVSS 10.0 is driven by the combination of no authentication, no user interaction, network attack vector, scope change, and full confidentiality/integrity impact on the backing datastore — not by code execution on the host.
Version boundary: NVD lists the affected range as < 3.39.12, but the researcher’s own empirical testing (documented in ANALYSIS.md) found the bare-quote injection vector used by this PoC is only exploitable on <= 3.39.0 — the templating layer already escapes injected quotes from 3.39.1 onward, well before the explicit processJsonStringSync fix lands in 3.39.9. The PoC and lab in this folder correctly target 3.39.0, the latest release inside the vendor advisory’s actually-affected range, rather than the coarser NVD boundary.
lab/provision.py provisions the vulnerable configuration entirely through Budibase’s own builder REST APIs (create admin, create app, create datasource, create queries, set PUBLIC role, publish) — it does not hand-edit internal state or weaken any default; marking a query PUBLIC is an intended, first-class builder feature that published apps rely on to serve data to anonymous visitors. EVIDENCE.txt, referenced in the upstream README as a captured full run, is not present in the upstream git repository (confirmed via git clone --depth 1 of the source repo) and so is not included here.
| |