
Prova de conceito de laboratório demonstrando uma vulnerabilidade crítica de bypass de autenticação no acceptInvite do GraphQL do Lago, que permite a tomada de conta entre organizações por meio da aceitação de convites sem autenticação.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-invite-ato
Lago v1.53.0 - GetLago
Mutations::Invites::Accept não possui AuthenticableApiUser. Create/revoke possuem. Accept não. register_from_invite carrega o usuário pelo e-mail do convite. Se essa conta já possui memberships, a senha enviada é descartada. AuthToken.encode(user:) ainda é executado. O JWT é com escopo de usuário. Sem claim de org. O cliente envia x-lago-organization para qualquer org à qual esse usuário já pertença. Incluindo a da vítima. createInvite já retorna o token para quem convida.
Convide cfo@victim para uma org descartável, aceite você mesmo, e você é essa pessoa em toda org em que esse e-mail já esteja. Caixa de correio nunca envolvida. Senha da vítima inalterada.
| ID | sem CVE ainda |
| CWE | CWE-287, CWE-306 |
| CVSS | Crítico: 9.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| Produto | Lago / lago-api |
| Afetado | até v1.53.0 acceptInvite por e-mail/senha |
| Auth | admin da org (organization:members:create); cadastro na Cloud é suficiente |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1 |
Você precisa de uma org que você controle. O cadastro na Cloud é suficiente (LAGO_DISABLE_SIGNUP é false por padrão). Convide um e-mail de vítima, aceite sem autenticação com uma senha fictícia, depois troque o header para Victim Corp. Faturas, chaves de API, Stripe, carteiras.
Não é um visitante aleatório sem conta. Não é um reset de senha. O login fictício falha. A senha real da vítima ainda funciona. O accept via Google/Okta vincula o e-mail do IdP e não é este bug. Self-host de org única sem o e-mail de outra pessoa no banco não te dá outro comerciante. Mesmo banco de dados, duas orgs, mesmo e-mail: dá.
Mesma árvore do IDOR de fatura única do Stripe. Bug diferente.
Eu li Accept (sem AuthenticableApiUser), depois find_or_initialize_by(email:), depois o branch de memberships presentes que pula a senha, depois AuthToken.encode(user:). Depois o accept via Google, que verifica google_oidc["email"] == invite.email. O accept por e-mail/senha não vincula a senha de um usuário existente.
O primeiro cliente que olhar para isso vai convidar um endereço não utilizado, aceitar, e obter um usuário Lago totalmente novo com a senha que enviou. Essa é a funcionalidade. A testemunha é o id do usuário vítima existente retornando.
Caminhos errados já registrados: aceitar com Google/Okta; tratar o 401 de createInvite sem um JWT como a história completa (create é autenticado, accept não é); olhar apenas para a org do atacante em currentUser (o indício é Victim Corp na lista); login com senha fictícia (isso significaria que o hash foi sobrescrito - o lab afirma que não foi); um reverse shell. Teatro. Sem caixa de entrada.
Então: duas chamadas registerUser plantam Victim Corp e Attacker Corp. E-mails novos a cada execução para que um volume sujo não retorne 422 user_already_exists. createInvite no e-mail da vítima. acceptInvite sem autenticação com uma senha fictícia. O id de usuário retornado corresponde ao da vítima. currentUser.organizations lista ambas. A troca de header retorna Victim Corp.
cd lab
./run.sh
Alvo apenas http://127.0.0.1:13000. O IDOR do Stripe usou 13001. Sem porta de UI.
IOC victim_password_unchanged
IOC victim_real_password_still_works
SUCCESS LAGO-INVITE-ATO acceptInvite JWT is existing victim; lists Victim Corp; no inbox
Se o usuário já existe, exija que ele se autentique como ele mesmo (ou prove a caixa de correio / IdP) antes de anexar a membership. Não emita um JWT para um usuário existente a partir de um accept não autenticado. Pare de retornar o token do convite para quem convida se ele for uma capability.
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0. Veja LICENSE. Apenas lab em loopback. Sem garantia.