VYPR
Critical severity9.0NVD Advisory· Published Jul 24, 2026· Updated Aug 12, 2026

Budibase: OIDC SSO account takeover: incoming identity linked by email without checking email_verified

CVE-2026-73302

Description

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:59email: 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 every configured IdP enforces it. The defect is unconditional on the Budibase side; the attack requirement is purely the (default, common) IdP email policy.

PoC

Reproduced live on Budibase 3.39.14 (self-hosted, community license) against a stock Keycloak 26 realm budi with default "Verify Email" = off; OIDC client registered and activated in Budibase.

Setup: a pre-existing victim global-admin Budibase account [email protected] (_id = us_1ab2dfcf…, local account, *no IdP link*). The attacker owns their own IdP account (attacker, distinct sub) and can set its email attribute unverified.

**Step 1 — the IdP asserts the claim (proves email_verified=false):** `` POST /realms/budi/protocol/openid-connect/token (Keycloak) grant_type=password&client_id=budibase&client_secret=…&username=attacker&password=Attacker123!&scope=openid email profile -> id_token payload: { "sub":"3cf58c45-…", "preferred_username":"attacker", "email":"[email protected]", "email_verified":false } ``

The authenticated principal is provably **attacker** (its own sub/preferred_username/password), merely *claiming* the victim's email, unverified.

Step 2 — drive the standard OIDC flow as attacker: GET /api/global/auth/default/oidc/configs/kc-oidc-1 -> IdP login as attacker/Attacker123! -> GET /api/global/auth/oidc/callback?code=…&state=….

Step 3 — result (takeover): Budibase sets budibase:auth to a session JWT { "userId":"us_1ab2dfcf…", "email":"[email protected]", "tenantId":"default" }, and GET /api/global/self returns the victim: _id = us_1ab2dfcf…, admin.global = true, builder.global = true, providerType = oidc. The attacker authenticated as a *different* IdP principal with an *unverified* email yet now holds a full global-admin session for the victim.

Negative control (proves the email claim is the cause, not a normal self-login): A second attacker attacker2 with a benign unverified email [email protected] (matching no Budibase user) runs the *identical* flow: `` id_token: { "sub":"55794bf2-…", "preferred_username":"attacker2", "email":"[email protected]", "email_verified":false } -> budibase:auth: { "userId":"us_55794bf2-…" } (a NEW account, _id derived from the IdP sub) -> /api/global/self: { "_id":"us_55794bf2-…", "email":"[email protected]", admin.global: null, builder.global: null } ``

With a benign email the attacker gets their own new low-privilege account; only when the email claim equals the victim's does the same flow yield the victim's admin account. Same self-authentication, single variable changed = the unverified-email merge is the vulnerability.

Impact

Takeover of any existing Budibase account by email, including the instance owner / global admin -> full control of the tenant (apps, datasources, automations, user management, stored datasource credentials). The attacker authenticates as their *own* (different) IdP principal and ends up holding the victim's session and roles. The only requirement beyond a trusted IdP login is that the IdP assert the victim's email unverified — the default for a freshly-created Keycloak/Authentik realm and common in social logins (see Preconditions). Any deployment that trusts an OIDC IdP without enforced email verification is exposed; the Budibase-side flaw (ignoring email_verified) is unconditional.

Remediation

Primary fix: in the OIDC verify path, require email_verified === true before using email to look up / link an existing account; otherwise reject the login (or fall back to sub-only matching and never merge into a pre-existing local/SSO account). Concretely, thread the email_verified claim through buildJwtClaims/getEmail (oidc.ts) and gate the getGlobalUserByEmail fallback in sso.ts:57-59 on it.

Audit the whole class: apply the same email_verified (and, for SAML, EmailVerified/assertion-signature) gate to every SSO strategy that links by email — OIDC, SAML, and any social provider — not only the Google strategy (which already passes requireLocalAccount=true). Email-based account linking anywhere must require a verified email.

Defense-in-depth for operators who cannot patch immediately: - On the IdP, enable "Verify Email" / require verified email before issuing tokens (Keycloak: realm -> Login -> Verify Email = on), and restrict which email domains the IdP will assert. - Prefer sub-based account mapping over email in the IdP/Budibase mapping config where available. - Audit existing accounts for unexpected OIDC links to privileged users; rotate sessions.

Affected products

1

Patches

Vulnerability mechanics

References

4

News mentions

0

No linked articles in our index yet.