A AegiFlow
HIGH

GHSA-c8vc-7pv3-g98p

Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)

Published
2026-07-24
Modified
2026-07-24
Sources
github-advisory

Summary

## Summary `POST /api/v2/email` on the account portal (`account.budibase.app`) starts an email-change workflow using a client-supplied `accountId` that is **not validated against the authenticated session**. A logged-in attacker supplies a victim's `accountId` and an email address they control; the verification code is delivered to the attacker's address, and completing the workflow changes the **victim's** account email. The attacker then password-resets the victim's account through the controlled address and logs in — full account takeover with no victim interaction. ## Vulnerability Details The endpoint session-checks the `currentEmail` field (a mismatch returns 403), but it does not enforce `body.accountId === session.accountId`. The account-portal frontend only ever submits the logged-in user's own accountId, so in normal use the two always match — the server simply trusts the body value. An attacker who has a victim's `accountId` can point the workflow at the victim's account while passing their own `currentEmail` to clear the session check. This is more powerful than a direct password reset: `PUT /api/v2/auth/password {email}` already exists and is gated only on knowing the victim's email, but the reset link is sent to the address on the account — the victim's inbox, which the attacker does not control. This IDOR moves the victim's email to an attacker-controlled inbox first, so the attacker receives the reset link and sets a password they know. **accountId obtainability.** We checked every endpoint reachable from a non-admin free-tier account — `GET /api/global/users` (no UUID in any user record), `GET /api/global/auditlogs/search` (no accountId field), `GET /api/global/users/invites` (code + email + name only), `GET /api/v2/tenant` (one tenant-internal UUID, not a user accountId), the account-portal frontend (passes the session's own accountId only), and all unauthenticated endpoints — and the victim's `accountId` was not present in any of them. The attacker must obtain the UUID through some channel other than the normal API surface (support channel, screenshot, a leak, prior compromise, etc.). ## Steps to Reproduce **Setup:** Two account-portal accounts at `https://account.budibase.app` — Account A (attacker) and Account B (victim). From Account B's session, `GET /api/auth/self` returns `account.accountId` — this is the value the attacker needs. 1. **Start the email-change workflow pointed at the victim's accountId, newEmail = attacker-controlled.** ```http POST /api/v2/email HTTP/1.1 Host: account.budibase.app Content-Type: application/json Cookie: budibase:auth= {"currentEmail":" ","newEmail":"[email protected]","accountId":" "} ``` Response: `201 Created`, sets `budibase:change_email:correlationkey` (HttpOnly), `:instancekey`, `:status`, `:newemail` cookies. The workflow started against the victim's accountId even though the caller is Account A. 2. **Complete the change with the code from the attacker-controlled inbox (or read directly from the `correlationkey` cookie set in step 1).** ```http POST /api/v2/email/verification HTTP/1.1 Host: account.budibase.app Content-Type: application/json Cookie: budibase:auth= ; budibase:change_email:correlationkey= ; budibase:change_email:instancekey= {"verificationCode":" ","processInstanceKey":" "} ``` Response: `200 OK`. The victim's account email is now `[email protected]`. 3. **Confirm the change hit the victim, not the caller (false-positive control).** - Account A (caller) still logs in with its own original email — 200, unchanged. - Account B's (victim) original email is now rejected — 403, locked out. - `[email protected]` now logs into the victim's account — 200. 4. **Take over the account.** ```http PUT /api/v2/auth/password HTTP/1.1 Host: account.budibase.app Content-Type: application/json {"email":"attacker-controlled@exam

Affected packages

EcosystemPackageAffected versionsFixed 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.