### Resumo O login do OIDC SSO da Budibase liga uma identidade SSO entrante a uma conta do Budibase existente **por endereço de e- mail sozinho**, sem nunca verificar a reivindicação `email_verified` do token do ID da OIDC. Budibase tenta primeiro combinar com o IdP `sub`; quando isso falha (qualquer conta IdP do atacante novo) ele silenciosamente volta a corresponder com o pedido de `email` e ** se funde na conta existente por e- mail**, preservando o `_id' e os papéis dessa conta. Como a bandeira `email_verified` nunca é lida, um atacante que pode fazer um ** configurado/ confiável** IdP emite um token carregando `email = ` com `email_verified = false` é logado no Budibase **como a vítima**, herdando os papéis da vítima (incluindo administrador/ construtor global). Por núcleo OIDC § 5.7 o pedido `email` NÃO DEVE ser usado como uma chave de identidade a menos que `email_verified` seja `verdadeiro`; o Budibase efetivamente delega toda a confiança de ligação de contas para cada política de verificação de e- mail do IdP configurada enquanto não verifica nada. A tomada de conta completa de qualquer usuário do Budibase existente, incluindo o proprietário da instância. ### Detalhes O OIDC verifica o callback extrai o e- mail e nunca consulta `email_verified`: - `pacotes/backend-core/src/middleware/passport/sso/oidc.ts: 59 ` — `email: getEmail(profil, jwtClaims)`. - `getEmail` (`oidc.ts: 113 - 135 `) retorna `profile._json.email` ->`jwtClaims.email` -> `preferido_nome de usuário`. **Sem verificação `email_verified`.** - `buildJwtClaims` (`oidc.ts: 99 - 107 `) monta os pedidos de `_json.email`/`emails[ 0 ].value` — nenhuma bandeira de verificação é lida. `grep -r email_verified packages/` -> 0 Acertos. O e- mail é então usado como a chave ** de ligação de contas**: - `sso.authenticate(...)` -> `pacotes/ backend-core/ src/middleware/ passaporte/ sso/ sso.ts`: - `: 38,44 ` ` users.getById(generateGlobalUserID(details.userId))` teclado no IdP `sub'; para uma conta IdP do atacante novo 404 s e é engolido (`: 45 - 54 `). - `: 57 - 59 ` **caixa:** `dbUser = aguarde usuários.getGlobalUserByEmail(details.email)` -> carrega a conta ** da vítima** (vítima `_id` + papéis) puramente por e- mail (`pacotes/backend-core/src/ users/ users.ts: 100 - 124 `, 'USER_BY_ EMAIL` view, sem vinculação ao IdP `sub`. - `syncUser((...)' (` ssso.ts: 80,102 - 138 `) espalha `... user`, preservando a vítima `_id`/` locantId`/`roles`; somente sobrescreve os campos do provedor. - `UserDB.save` (`pacotes/backend-core/src/ users/db.ts: 235 `): porque ` ssoUser._id` é da vítima, o ramo `_id` corre (`: 253 "), "getById(_id)" corresponde à vítima (`: 256 `), o "Endereço de E- mail não pode ser alterado" guarda (`: 257 - 259 `) não dispara (`dbUser.email === email`), e a guarda `EmailUndisponibleError` (`: 269 - 275 `) é ignorado (só é executado no ramo `!dbUser`). A fusão prossegue em silêncio; uma sessão JWT é emitida para a vítima.
Por núcleo OIDC § 5.7, a reivindicação `email` NÃO DEVE ser usada como chave de identidade a menos que `email_verified` seja `verdadeira'. O Budibase nunca lê a bandeira. **Precondições (requisito de ataque — `AT:P`):** o atacante deve ser capaz de autentificar através de um IdP que a instância de Budibase ** confie** E obter que IdP para afirmar o e- mail da vítima com `email_verified = false`. Isto é alcançável, não exótico: - **Auto- registro com um e- mail não verificado — Keycloak e Authentik nave com *"Verify Email" OFF por padrão*; se o IdP confiável permite o registro público, o atacante registra uma nova conta e simplesmente entra `email = ' ao se inscrever. Não é necessário e- mail de confirmação — o IdP armazena e afirma que não foi verificado.** - **Edição de perfil de auto- serviço** — muitos IdPs permitem que um usuário logado mude seu próprio e- mail sem re- verificação forçada. - **IdP operado/ federado ou login social permissivo** — onde o atacante controla ou influencia um provedor confiável, ou o provedor afirma um e- mail tipificado pelo usuário (não verificado). É ** não** explorável através de um IdP corporativo estrito que obriga a verificação de e- mail (há `email_verified = true` e o atacante não pode reivindicar o endereço da vítima) — e é exatamente por isso que o Budibase deve verificar a bandeira em vez de assumir que cada IdP configurado o obriga a executar. O defeito é incondicional no lado de Budibase; o requisito de ataque é puramente a política de e- mail IdP (predefinida, comum).
### PoC Reproduzida ao vivo em Budibase ` 3.39.14 ` (auto- hospedada, licença da comunidade) contra um estoque **Keycloak 26 ** reino `budi` com padrão "Verify Email" = off; cliente OIDC registrado e ativado em Budibase. ** Configuração:** uma conta pré- existente ** vítima** administrador global Budibase ` victim@stand.local` (`_id = us_ 1 ab 2 dfcf...`, conta local, * nenhum link IdP*). O atacante possui a sua conta IdP **proprio** (`ataque`, 'sub`) e pode definir o seu atributo de e- mail não verificado.
**Passo 1 — o IdP afirma a reivindicação (prova `email_verified=false`):** ``` POST /realms/budi/protocol/openid-connect/token (Keycloak) grant_type=password&client_id=budibase&client_secret=...&username=atacker&password=Attacker 123!&scope=penid email profile -> id_token payload: { "sub":" 3 cf 58 c 45 -...", "preferred_username":"atacar", "email":"victim@stand.local", "email_verified":false } ``` O principal autenticado é, de forma comprovada, **`atacar`** (o seu próprio `sub`/`prefered_username`/password), meramente *reclamando* o e- mail da vítima, não verificado. **Passo 2 — dirija o fluxo padrão OIDC** como 'ataque': `GET /api/global/auth/default/oidc/configs/kc-oidc- 1 ` -> IdP login como `ataque`/`Attacker 123!` -> `GET /api/ global/ auth/ oidc/ callback?code=...&state=...`.
**Passo 3 — resultado (toma):** Budibase define `budibase:auth` para uma sessão JWT `{ "usernaid":"us_ 1 ab 2 dfcf...", "email":"victim@stand.local", "tenantId":"default" }`, e `GET /api/global/self` retorna a **víctima**: `_id = us_ 1 ab 2 dfcf...`, `admin.global = true`, `builder.global = true`, `providerType = oidc`. O atacante autenticado como um principal *diferente* IdP com um e- mail *não verificado*, ainda agora tem uma sessão global-admin completa para a vítima. ** Controle negativo (prova que o pedido de e- mail é a causa, não um auto- login normal):** Um segundo atacante ` atacante 2 ` com um 'ataque' de e- mail não verificado 2 @ evil.local` (que não combina com o usuário do Budibase) executa o fluxo * idêntico*: ``` id_ token: { "sub":" 55794 bf 2 -...", "preferido_nome de usuário":"ataque 2 ", "email":"ataque 2 @ evil.local", "email_verified":false } -> bubisa:auth: { "usernaid":"us_ 55794 bf 2 -..." } (uma nova conta, _id derivado do sub- IdP) -> /api/global/self: { "_id":"us_ 55794 bf 2 -...", "email":"ataque 2 @ evil.local", admin.global: null, builder.global: null } ```.
Com um e- mail benigno o atacante recebe ** sua própria conta de baixo privilégio**; somente quando a reivindicação de e- mail é igual à da vítima faz o mesmo fluxo, produz a conta de administrador da ** vítima**. A mesma autoautenticação, única variável mudada = a fusão de e- mail não verificada é a vulnerabilidade. ### Impacto Tomada de qualquer conta Budibase existente por e- mail, incluindo o proprietário da instância / administrador global -> controle total do locatário (apps, fontes de dados, automações, gerenciamento de usuário, credenciais de fonte de dados armazenadas). O atacante autentica- se como o seu principal * próprio* (diferente) IdP e acaba mantendo a sessão e os papéis da vítima. A única exigência além de um login IdP confiável é que o IdP afirme o e- mail da vítima não verificado — o padrão para um reino Keycloak/ Authentik recém criado e comum em logins sociais (ver Precondições). Qualquer implantação que confie em um IdP OIDC sem verificação de e- mail forçada é exposta; o defeito do lado de Budibase (ignorando `email_ verified`) é incondicional.
### Remediação **Reparação inicial:** no caminho de verificação do OIDC, requeira `email_verified === true` antes de usar `email` para procurar/ ligar uma conta existente; caso contrário, rejeitar o login (ou voltar para ‘sub' apenas combinando e nunca se fundir em uma conta local/ SSO pré- existente). Concretamente, encha a reivindicação `email_verified` através de `buildJwtClaims`/ `getEmail` (`oidc.ts`) e porte o retalho `getGlobalUserByEmail` em `sso.ts: 57 - 59 " nele. ** Verifique a classe inteira:** aplique o mesmo `email_verified` (e, para SAML, `EmailVerified`/assertion-signature) portão para cada estratégia SSO que liga por e- mail — OIDC, SAML e qualquer provedor social — não somente a estratégia do Google (que já passa por `requireLocalAccount=true`). A ligação de conta baseada em e- mail em qualquer lugar deve requerer um e- mail verificado.
** Defesa em profundidade para os operadores que não podem fazer a correção imediatamente:** - No IdP, ative "Verificar Email" / requeira e- mail verificado antes de emitir tokens (Keycloak: reino -> Login -> Verificar Email = on), e restrinja quais domínios de email o IdP irá afirmar. - Preferir mapeamento de conta baseado em `sub` por email na configuração de mapeamento IdP/ Budibase quando disponível. - Auditar contas existentes para links inesperados do OIDC para usuários privilegiados; rotar sessões. Registro de aconselhamento: GHSA- hp 6 v- 6 jw 7 - gv 2 f. Identificadores relacionados: CVE- 2026 - 73302.
Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 24 T 21: 17: 39.000 Z e lista a sua última modificação como 2026 - 08 - 12 T 18: 57: 08.000 Z. Gravidade: Crítico. Dados de pontuação publicados: CVSS_V 4: CVSS: 4.0 /AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/África do Sul:H.
Informações sobre software e versão afetadas: pacote npm @budibase/ server — ECOSISTEM: introduzido 0, última afetada 3.38.1. Classificação e evidência: identificadores de fraqueza CWE- 287. O registro contém 4 suporte de referências nestes tipos: WEB, PACKAGE.