
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
Lago v1.53.0 - GetLago
Mutations::Invites::Accept hat kein AuthenticableApiUser. Create/revoke schon. 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 Organisation, 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-Organisation ein, nimm die Einladung selbst an, und du bist diese Person in jeder Organisation, in der diese E-Mail bereits sitzt. Postfach nie beteiligt. Opfer-Passwort 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 E-Mail/Passwort acceptInvite |
| Auth | Org-Admin (organization:members:create); Cloud-Signup reicht aus |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Du brauchst eine Organisation, die du kontrollierst. Cloud-Signup reicht aus (LAGO_DISABLE_SIGNUP ist standardmäßig false). Lade eine Opfer-E-Mail ein, nimm die Einladung unauthentifiziert mit einem Dummy-Passwort an, dann wechsle per Header zu Victim Corp. Rechnungen, API-Schlüssel, Stripe, Wallets.
Kein zufälliger Besucher ohne Konto. Kein Passwort-Reset. Dummy-Login schlägt fehl. Das echte Opfer-Passwort funktioniert weiterhin. Google/Okta-Accept bindet die IdP-E-Mail und ist nicht dieser Bug. Single-Org-Self-Host, bei dem niemand anderes E-Mail in der DB hat, gibt dir keinen anderen Merchant. Gleiche Datenbank, zwei Orgs, gleiche E-Mail: dann schon.
Gleicher Baum wie der Stripe One-Time Invoice IDOR. Anderer Bug.
Ich habe Accept gelesen (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, der sehr wohl google_oidc["email"] == invite.email prüft. E-Mail/Passwort-Accept bindet nicht das Passwort eines existierenden Benutzers.
Der erste Client, der sich das ansieht, lädt eine unbenutzte Adresse ein, nimmt die Einladung an und bekommt einen brandneuen Lago-Benutzer mit dem übermittelten Passwort. Das ist das Feature. Der Beweis ist die existierende Opfer-Benutzer-ID, die zurückkommt.
Bereits dokumentierte Irrwege: Annehmen mit Google/Okta; createInvite 401 ohne JWT als die ganze Geschichte behandeln (create ist authentifiziert, accept nicht); nur die Angreifer-Org bei currentUser 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 Postfach.
Dann: zwei registerUser-Aufrufe legen Victim Corp und Attacker Corp an. Frische E-Mails bei jedem Lauf, damit ein schmutziges Volume kein 422 user_already_exists wirft. createInvite auf die Opfer-E-Mail. Unauthentifiziertes acceptInvite mit einem Dummy-Passwort. Zurückgegebene Benutzer-ID stimmt mit dem Opfer überein. currentUser.organizations listet beide auf. Header-Wechsel liefert Victim Corp.
cd lab
./run.sh
Ziel nur http://127.0.0.1:13000. Stripe IDOR nutzte 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, verlange, dass er sich als er selbst authentifiziert (oder Postfach / IdP nachweist), bevor die Mitgliedschaft angehängt wird. Stelle kein JWT für einen existierenden Benutzer aus einem unauthentifizierten Accept aus. Höre auf, das Einladungs-token an den Einladenden 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.