
Proof-of-concept lab che dimostra un bypass di autenticazione critico in Lago GraphQL acceptInvite che consente il takeover di account tra organizzazioni tramite l'accettazione di inviti non autenticata.
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 non ha AuthenticableApiUser. Create/revoke sì. Accept no. register_from_invite carica l'utente tramite l'email dell'invito. Se quell'account ha già delle membership, la password inviata viene scartata. AuthToken.encode(user:) viene comunque eseguito. Il JWT è user-scoped. Nessun claim di org. Il client invia x-lago-organization per qualunque org a cui quell'utente appartiene già. Inclusa quella della vittima. createInvite restituisce già il token all'invitante.
Invita cfo@victim in un'org usa e getta, accetta tu stesso l'invito, e sei quella persona su ogni org in cui quell'email è già presente. La casella di posta non è mai coinvolta. La password della vittima resta invariata.
| ID | nessuna CVE al momento |
| CWE | CWE-287, CWE-306 |
| CVSS | Critico: 9.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| Prodotto | Lago / lago-api |
| Affetto | fino alla v1.53.0 acceptInvite con email/password |
| Auth | admin dell'org (organization:members:create); la registrazione Cloud è sufficiente |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Serve un'org sotto il tuo controllo. La registrazione Cloud è sufficiente (LAGO_DISABLE_SIGNUP è false per impostazione predefinita). Invita l'email di una vittima, accetta senza autenticazione con una password fittizia, poi cambia header verso Victim Corp. Fatture, chiavi API, Stripe, wallet.
Non è un visitatore casuale senza account. Non è un reset della password. Il login con la password fittizia fallisce. La password reale della vittima funziona ancora. L'accept con Google/Okta associa l'email dell'IdP e non è questo bug. Un self-host con una sola org e nessuna email di altri nel DB non ti dà accesso a un altro merchant. Stesso database, due org, stessa email: sì.
Stesso albero dell'IDOR sull'ID fattura una tantum di Stripe. Bug diverso.
Ho letto Accept (nessun AuthenticableApiUser), poi find_or_initialize_by(email:), poi il ramo in cui le membership sono presenti che salta la password, poi AuthToken.encode(user:). Poi l'accept con Google, che verifica google_oidc["email"] == invite.email. L'accept con email/password non associa la password di un utente esistente.
Il primo client che esamina questo comportamento inviterà un indirizzo inutilizzato, lo accetterà e otterrà un utente Lago nuovo di zecca con la password che ha inviato. Questa è la funzionalità. La prova è l'id dell'utente vittima esistente che ritorna.
Vicoli ciechi già registrati: accettare con Google/Okta; considerare il 401 di createInvite senza JWT come l'intera storia (create è autenticato, accept no); guardare solo l'org dell'attaccante su currentUser (l'indizio è Victim Corp nella lista); il login con la password fittizia (significherebbe che l'hash è stato sovrascritto - il lab verifica che non lo sia stato); una reverse shell. Teatro. Nessuna casella di posta.
Poi: due chiamate registerUser inseriscono Victim Corp e Attacker Corp. Email nuove a ogni esecuzione, così un volume sporco non restituisce 422 user_already_exists. createInvite sull'email della vittima. acceptInvite non autenticato con una password fittizia. L'id utente restituito corrisponde alla vittima. currentUser.organizations elenca entrambe. Il cambio di header restituisce Victim Corp.
cd lab
./run.sh
Prendi di mira solo http://127.0.0.1:13000. L'IDOR di Stripe utilizzava 13001. Nessuna porta 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 l'utente esiste già, richiedi che si autentichi come se stesso (o provi la casella di posta / IdP) prima di associare l'appartenenza. Non generare un JWT per un utente esistente da un'accettazione non autenticata. Smetti di restituire il token dell'invito all'invitante se si tratta di una capability.
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0. Vedi LICENSE. Solo laboratorio loopback. Nessuna garanzia.