
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
Write-up: https://abraxaslabs.tech/research/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 é user-scoped. 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á está. Caixa de entrada 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 controle. Cadastro na Cloud é suficiente (LAGO_DISABLE_SIGNUP tem padrão false). 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 com a senha fictícia falha. A senha real da vítima continua funcionando. 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, duas orgs, mesmo e-mail: dá.
Mesma árvore do IDOR de fatura única do Stripe. Bug diferente.
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 de fato 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 novinho em folha com a senha que ele enviou. Isso é a feature. A testemunha é o id do usuário vítima existente voltando.
Caminhos errados já registrados: aceitar com Google/Okta; tratar o 401 de createInvite sem JWT como a história toda (create é autenticado, accept não é); olhar apenas para a org do atacante em currentUser (o indício é Victim Corp na lista); a senha fictícia logar (isso significaria que o hash foi sobrescrito - o lab afirma que não foi); um reverse shell. Teatro. Sem caixa de entrada.
Em seguida: duas chamadas registerUser criam 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 não autenticado com uma senha fictícia. O id de usuário retornado corresponde ao da vítima. currentUser.organizations lista ambas. A troca de cabeçalho retorna Victim Corp.
cd lab
./run.sh
Aponte apenas para 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 comprove a caixa de correio / IdP) antes de anexar a associação. Não emita um JWT para um usuário existente a partir de uma aceitação não autenticada. Pare de retornar o token do convite ao convidador se ele for uma capacidade.
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0. Consulte LICENSE. Apenas laboratório de loopback. Sem garantia.