GHSA-hp6v-6jw7-gv2f
Budibase: OIDC SSO account takeover: incoming identity linked by email without checking email_verified
Summary
### Summary Budibase's OIDC SSO login links an incoming SSO identity to an existing Budibase account **by email address alone**, without ever checking the `email_verified` claim of the OIDC ID token. Budibase first tries to match the IdP `sub`; when that misses (any fresh attacker IdP account) it silently falls back to matching by the `email` claim and **merges into the existing account by email**, preserving that account's `_id` and roles. Because the `email_verified` flag is never read, an attacker who can make a **configured/trusted** IdP emit a token carrying `email = ` with `email_verified = false` is logged into Budibase **as the victim**, inheriting the victim's roles (including global admin/builder). Per OIDC Core §5.7 the `email` claim MUST NOT be used as an identity key unless `email_verified` is `true`; Budibase effectively delegates all account-linking trust to every configured IdP's email-verification policy while checking nothing itself. Full account takeover of any existing Budibase user, including the instance owner. ### Details The OIDC verify callback extracts the email and never consults `email_verified`: - `packages/backend-core/src/middleware/passport/sso/oidc.ts:59` — `email: getEmail(profile, jwtClaims)`. - `getEmail` (`oidc.ts:113-135`) returns `profile._json.email` ->`jwtClaims.email` -> `preferred_username`. **No `email_verified` check.** - `buildJwtClaims` (`oidc.ts:99-107`) assembles claims from `_json.email`/`emails[0].value` — no verification flag is read. `grep -r email_verified packages/` -> 0 hits. The email is then used as the **account-linking key**: - `sso.authenticate(...)` -> `packages/backend-core/src/middleware/passport/sso/sso.ts`: - `:38,44` `users.getById(generateGlobalUserID(details.userId))` keyed on the IdP `sub`; for a fresh attacker IdP account this 404s and is swallowed (`:45-54`). - `:57-59` **fallback:** `dbUser = await users.getGlobalUserByEmail(details.email)` -> loads the **victim's** account (victim `_id` + roles) purely by email (`packages/backend-core/src/users/users.ts:100-124`, `USER_BY_EMAIL` view, no binding to the IdP `sub`). - `syncUser(...)` (`sso.ts:80,102-138`) spreads `...user`, preserving the victim `_id`/`tenantId`/`roles`; only overwrites provider fields. - `UserDB.save` (`packages/backend-core/src/users/db.ts:235`): because `ssoUser._id` is the victim's, the `_id` branch runs (`:253`), `getById(_id)` matches the victim (`:256`), the "Email address cannot be changed" guard (`:257-259`) does not fire (`dbUser.email === email`), and the `EmailUnavailableError` guard (`:269-275`) is skipped (it only runs in the `!dbUser` branch). The merge proceeds silently; a session JWT is issued for the victim. Per OIDC Core §5.7, the `email` claim MUST NOT be used as an identity key unless `email_verified` is `true`. Budibase never reads the flag. **Preconditions (attack requirement — `AT:P`):** the attacker must be able to authenticate through an IdP that the Budibase instance **trusts** AND get that IdP to assert the victim's email with `email_verified = false`. This is reachable, not exotic: - **Self-registration with an unverified email — Keycloak and Authentik ship with *"Verify Email" OFF by default*; if the trusted IdP allows public sign-up, the attacker registers a new account and simply enters `email = ` at sign-up. No confirmation email is needed — the IdP stores and asserts it unverified.** - **Self-service profile editing** — many IdPs let a logged-in user change their own email without forced re-verification. - **Attacker-operated / federated IdP or permissive social login** — where the attacker controls or influences a trusted provider, or the provider asserts a user-typed (unverified) email. It is **not** exploitable through a strict corporate IdP that enforces email verification (there `email_verified = true` and the attacker cannot claim the victim's address) — which is exactly why Budibase must check the flag rather than assume ev
Affected packages
| Ecosystem | Package | Affected versions | Fixed versions |
|---|---|---|---|
| npm | @budibase/server | — | — |
Remediation: No patched version is listed by GitHub.
References
Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.