
Proof-of-Concept-Lab, das einen kritischen Lago GraphQL acceptInvite-Authentifizierungs-Bypass demonstriert, der eine organisationsübergreifende Kontoübernahme durch unauthentifizierte Einladungsannahme ermöglicht.
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 hat kein AuthenticableApiUser. Create/revoke haben es. Accept nicht. register_from_invite lädt den Benutzer anhand der Einladungs-E-Mail. Wenn dieses Konto bereits Mitgliedschaften hat, wird das übermittelte Passwort verworfen. AuthToken.encode(user:) läuft trotzdem. Das JWT ist benutzerbezogen. Kein Org-Claim. Der Client sendet x-lago-organization für die Org, zu der dieser Benutzer bereits gehört. Einschließlich der des Opfers. createInvite gibt das Token bereits an den Einladenden zurück.
Lade cfo@victim in eine Wegwerf-Org ein, nimm die Einladung selbst an, und du bist diese Person in jeder Org, in der diese E-Mail bereits sitzt. Das Postfach ist nie beteiligt. Das Passwort des Opfers bleibt unverändert.
| ID | noch keine CVE |
| CWE | CWE-287, CWE-306 |
| CVSS | Kritisch: 9.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| Produkt | Lago / lago-api |
| Betroffen | bis v1.53.0 acceptInvite per E-Mail/Passwort |
| Auth | Org-Admin (organization:members:create); Cloud-Signup genügt |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Du brauchst eine Org, die du kontrollierst. Cloud-Signup genügt (LAGO_DISABLE_SIGNUP ist standardmäßig false). Lade eine Opfer-E-Mail ein, nimm die Einladung unauthentifiziert mit einem Dummy-Passwort an und wechsle dann per Header zu Victim Corp. Rechnungen, API-Keys, Stripe, Wallets.
Kein zufälliger Besucher ohne Konto. Kein Passwort-Reset. Der Dummy-Login schlägt fehl. Das echte Opfer-Passwort funktioniert weiterhin. Google/Okta-Accept bindet die IdP-E-Mail und ist nicht dieser Bug. Ein Single-Org-Self-Host, bei dem niemand anderes E-Mail in der DB hat, verschafft dir keinen Zugriff auf einen anderen Merchant. Dieselbe Datenbank, zwei Orgs, dieselbe E-Mail: dann schon.
Derselbe Baum wie der Stripe One-Time-Invoice-IDOR. Anderer Bug.
Ich las Accept (kein AuthenticableApiUser), dann find_or_initialize_by(email:), dann den Branch mit vorhandenen Mitgliedschaften, der das Passwort überspringt, dann AuthToken.encode(user:). Dann Google-Accept, das sehr wohl google_oidc["email"] == invite.email prüft. Accept per E-Mail/Passwort bindet nicht das Passwort eines bestehenden Benutzers.
Der erste Client, der sich das ansieht, wird eine ungenutzte Adresse einladen, die Einladung annehmen und einen brandneuen Lago-Benutzer mit dem übermittelten Passwort erhalten. Das ist das Feature. Der Beweis ist die bestehende Opfer-Benutzer-ID, die zurückkommt.
Bereits dokumentierte Irrwege: Annehmen mit Google/Okta; den 401 von createInvite ohne JWT als die ganze Geschichte zu behandeln (create ist authentifiziert, accept nicht); nur die Angreifer-Org bei currentUser zu betrachten (der Hinweis ist Victim Corp in der Liste); Dummy-Passwort-Login (das würde bedeuten, der Hash wurde überschrieben - das Lab bestätigt, dass er es nicht wurde); eine Reverse Shell. Theater. Kein Posteingang.
Dann: Zwei registerUser-Aufrufe legen Victim Corp und Attacker Corp an. Bei jedem Lauf werden frische E-Mails verwendet, damit ein verunreinigtes Volume kein 422 user_already_exists auslöst. createInvite für die E-Mail des Opfers. Nicht authentifiziertes acceptInvite mit einem Dummy-Passwort. Die zurückgegebene Benutzer-ID stimmt mit der des Opfers überein. currentUser.organizations listet beide auf. Der Header-Wechsel liefert Victim Corp zurück.
cd lab
./run.sh
Ziele nur http://127.0.0.1:13000. Stripe IDOR verwendete 13001. Kein UI-Port.
IOC victim_password_unchanged
IOC victim_real_password_still_works
SUCCESS LAGO-INVITE-ATO acceptInvite JWT is existing victim; lists Victim Corp; no inbox
Wenn der Benutzer bereits existiert, verlangen Sie, dass er sich als er selbst authentifiziert (oder das Postfach / den IdP nachweist), bevor die Mitgliedschaft angehängt wird. Stellen Sie kein JWT für einen bestehenden Benutzer aus einer nicht authentifizierten Annahme aus. Hören Sie auf, dem Einladenden das Einladungs-token zurückzugeben, wenn es eine Capability ist.
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.