
PoC per CVE-2026-54350 — Budibase iniezione di operatori NoSQL non autenticata (CVSS 10.0). Leggere/scrivere in massa qualsiasi raccolta di documenti tramite una query PUBBLICA.
Iniezione di operatori JSON/NoSQL non autenticata in Budibase. Qualsiasi visitatore anonimo di un'app Budibase pubblicata che espone una query PUBBLICA supportata da un datasource documentale (MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL o REST con corpo JSON) può leggere ogni documento della collezione sottostante e, se esiste una query di scrittura pubblica, modificare ogni documento — con una singola richiesta HTTP non autenticata.
| CVE | CVE-2026-54350 |
| Avviso | 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 (Neutralizzazione impropria di elementi speciali in una query dati), CWE-89 |
| Autenticazione | Nessuna (non autenticata) |
| Versioni affette | Budibase <= 3.39.0 (intervallo dell'avviso del fornitore) |
| Versione corretta | 3.39.12 secondo NVD; le virgolette iniettate sono già neutralizzate da 3.39.1, e la correzione esplicita (processJsonStringSync) arriva in 3.39.9 |
| Verdetto | CONFERMATO — riprodotto end-to-end su 3.39.0 |
Quando esegue una query, Budibase arricchisce i parametri utente nel corpo JSON grezzo della query con Handlebars, poi esegue JSON.parse del risultato:
enrichContext() packages/server/src/sdk/workspace/queries/queries.ts
enrichedQuery.json = processStringSync('{"name":"{{ name }}"}', { name: <user> }, { noEscaping: true })
enrichedQuery.json = JSON.parse(enrichedQuery.json)
Nelle build interessate il parametro viene interpolato senza escape JSON, quindi un valore contenente un " chiude la stringa prevista e inietta chiavi correlate nell'oggetto analizzato. L'unico filtro di input, validateQueryInputs() (api/controllers/query/index.ts), rifiuta solo i marcatori Handlebars ({{, }}) — non tocca ", \, } o $.
Per un find di MongoDB, l'oggetto analizzato viene passato direttamente a collection.find() (integrations/mongodb.ts). Una chiave name duplicata il cui valore è un operatore oggetto vince l'unione JSON.parse, trasformando un'uguaglianza di stringa in {$exists: true} e restituendo l'intera collezione. La stessa primitiva contro una query updateMany allarga il filtro a ogni documento.
Il controllo degli accessi viene bypassato perché authorized() (middleware/authorized.ts:141) cortocircuita con return next() quando il ruolo della risorsa della query è PUBLIC, saltando sia l'autenticazione di sessione che CSRF. L'intestazione x-budibase-app-id necessaria per raggiungere la query è pubblica — fa parte di ogni URL di app pubblicata.
Vedi ANALYSIS.md per l'analisi completa a livello di codice e l'indagine sui confini di versione.
Template del corpo della query (come configurato nel builder):
{"name":"{{ name }}"}
Valore iniettato per il parametro name:
zzz","name":{"$exists":true},"$comment":"cve-2026-54350
Dopo l'interpolazione e JSON.parse (unione di chiavi duplicate, l'ultima vince):
{ "name": { "$exists": true }, "$comment": "cve-2026-54350" }
$comment è un meta-operatore MongoDB inerte che assorbe il "} finale del template, mantenendo il corpo JSON valido.
exploit.py — Solo libreria standard Python 3, nessuna dipendenza.
# Scarica l'intera collezione attraverso una query di lettura PUBBLICA (find)
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode read
# Modifica ogni documento attraverso una query updateMany PUBBLICA
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode write
--app-id e --query-id sono valori pubblici osservati nel traffico API dell'app pubblicata. --field sovrascrive la chiave JSON a cui è associato il parametro (predefinito su --param).
Richiede Docker. Questo laboratorio utilizza --network host (nessuna rete bridge presupposta) e mongo:4.4 (l'host non ha AVX; MongoDB 5.0+ lo richiede).
bash lab/setup.sh
# stampa PROD_APP_ID / READ_QUERY / UPDATE_QUERY, poi:
python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \
--query-id <READ_QUERY> --mode read
lab/provision.py utilizza solo API legittime del builder — non indebolisce alcuna impostazione predefinita. Contrassegnare il ruolo di accesso di una query come PUBLIC è una funzionalità di prima classe del builder (le app pubblicate espongono i dati esattamente attraverso questo meccanismo). Esecuzione completa catturata in EVIDENCE.txt.
NVD elenca l'intervallo interessato come < 3.39.12. Empiricamente il vettore delle virgolette semplici è sfruttabile solo su <= 3.39.0, che è esattamente l'intervallo indicato dall'avviso GHSA del fornitore. Da 3.39.1 il livello di templating già esegue l'escape delle virgolette iniettate (verificato: la stessa richiesta contro 3.39.8 è neutralizzata), e 3.39.9 introduce la correzione esplicita processJsonStringSync. Questo PoC si rivolge quindi a 3.39.0, l'ultima release all'interno dell'intervallo interessato del fornitore.
>= 3.39.12, o almeno >= 3.39.9 dove processJsonStringSync esegue l'escape JSON dei parametri interpolati).PUBLIC; limitarle, ove possibile, a ruoli autenticati.Cercare richieste POST /api/v2/queries/<id>, da sessioni non autenticate, i cui valori parameters contengono un " seguito da un operatore MongoDB con prefisso $ ($exists, $ne, $gt, $where, $regex, ...) o una chiave JSON duplicata. Su MongoDB, abilitare la profilazione e generare un avviso quando un parametro di query normalmente stringa arriva come oggetto.
Ricerca e PoC di Caio Fabrício (@BiiTts).