A AegiFlow
HIGHCVSS 7.1

GHSA-pmpg-2mxq-6xwr

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

Published
2026-07-24
Modified
2026-07-24
Sources
github-advisory

Summary

## Summary An end-user injection in Budibase's MongoDB datasource lets any BASIC app user bypass the builder's query-level access controls. Builders scope MongoDB reads per-user with bindings like `{"email": "{{ currentUser.email }}"}` so each app user only sees their own rows. Because the binding is handlebars-enriched into the query JSON with `noEscaping: true` and then `JSON.parse`d, Bob (a BASIC user) overrides the builder's filter with a MongoDB operator and reads every document the connection can touch. SQL datasources are parameterized through `interpolateSQL()`; the MongoDB path has no equivalent, so the scoping pattern Budibase's own docs show is unsafe. ## Details ### Enrichment `packages/server/src/sdk/workspace/queries/queries.ts:105-125` enriches every string field of the query with handlebars, then parses the enriched `json` field: ```typescript enrichedQuery[key] = processStringSync(fields[key], parameters, { noEscaping: true, noHelpers: true, escapeNewlines: true, }) // ... enrichedQuery.json = JSON.parse( enrichedQuery.json || enrichedQuery.customData || enrichedQuery.requestBody ) ``` `noEscaping: true` turns `{{name}}` into `{{{name}}}`, which Handlebars renders without HTML-escaping. Any `"` or `}` the attacker supplies lands verbatim in the enriched string. `JSON.parse` then produces a structured object whose top-level keys and operators came from the parameter value. ### Execution `packages/server/src/integrations/mongodb.ts:499-512`: ```typescript async read(query: MongoDBQuery) { try { await this.connect() const db = this.client.db(this.config.db) const collection = db.collection(query.extra.collection) let json = this.createObjectIds(query.json) switch (query.extra.actionType) { case "find": { if (json) { return await collection.find(json).toArray() } ``` `createObjectIds` walks the object and rewrites strings that look like `ObjectId(...)`. It does not strip `$`-prefixed keys and does not reject operator-shaped values. The injected filter reaches `collection.find` unchanged. For comparison, SQL datasources go through `interpolateSQL` at `packages/server/src/threads/query.ts:145`, which parameterizes bindings into driver-level bind variables. The MongoDB path has no equivalent. ### Duplicate-key trick The template gives the attacker one substitution point inside the `name` value. The outer `{"name": "` / `"}` is fixed. The attacker's payload: ``` x", "name": {"$ne": "x"}, "$comment": "bud-033 ``` Renders to: ```json {"name": "x", "name": {"$ne": "x"}, "$comment": "bud-033"} ``` `JSON.parse` keeps the last value for a duplicate key (ECMA-404 leaves this implementation-defined; V8 and Node's `JSON.parse` keep the last), so the parsed object is: ```js { name: { $ne: "x" }, $comment: "bud-033" } ``` `$comment` is a MongoDB meta operator that the server accepts and ignores, so it consumes the template's trailing `"}` without restricting the query. The filter that reaches MongoDB is `name != "x"`, which matches every document. ## Proof of Concept Tested against Budibase 3.35.8 (master at f960e361) and MongoDB 6. Step 1: Admin creates a MongoDB datasource and seeds three documents: ```bash 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:"[email protected]", secret:"public-alice"}, {name:"bob", email:"[email protected]", secret:"PRIVATE-BOB"}, {name:"eve", email:"[email protected]", secret:"PRIVATE-EVE"} ]);' ``` Step 2: Alice, a builder, configures the datasource and writes a MongoDB query `find-by-name` whose `json` field is `{"name": "{{name}}"}`. She publishes the app and grants Bob (BASIC) a role on it. Step 3: Bob, logged in as BASIC with a role on the published app, executes the query via the standard execute endpoint. `POST /api/queries/:queryId` is reachable by any app role with permission on t

Affected packages

EcosystemPackageAffected versionsFixed versions
npm@budibase/server

Remediation: No patched version is listed by GitHub.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.