
एक proof-of-concept lab जो एक critical Lago GraphQL acceptInvite authentication bypass को प्रदर्शित करता है जो unauthenticated invite acceptance के माध्यम से cross-organization account takeover को सक्षम बनाता है।
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-invite-ato
Lago v1.53.0 - GetLago
Mutations::Invites::Accept में AuthenticableApiUser नहीं है। Create/revoke में है। Accept में नहीं है। register_from_invite उपयोगकर्ता को invite email से लोड करता है। यदि उस खाते में पहले से memberships हैं, तो पोस्ट किया गया password त्याग दिया जाता है। AuthToken.encode(user:) फिर भी चलता है। JWT user-scoped है। कोई org claim नहीं। Client x-lago-organization उस org के लिए भेजता है जिससे वह उपयोगकर्ता पहले से संबंधित है। शिकार के org सहित। createInvite पहले से ही inviter को token लौटाता है।
cfo@victim को एक throwaway org में invite करें, इसे स्वयं accept करें, और आप उस व्यक्ति बन जाते हैं हर उस org पर जहाँ वह email पहले से मौजूद है। Mailbox कभी शामिल नहीं। शिकार का password अपरिवर्तित।
| ID | अभी तक कोई CVE नहीं |
| CWE | CWE-287, CWE-306 |
| CVSS | Critical: 9.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N |
| Product | Lago / lago-api |
| Affected | v1.53.0 तक email/password acceptInvite |
| Auth | org admin (organization:members:create); Cloud signup पर्याप्त है |
| License | GNU Affero GPL v3.0 |
| Lab | केवल 127.0.0.1 |
आपको एक org चाहिए जिसे आप नियंत्रित करते हैं। Cloud signup पर्याप्त है (LAGO_DISABLE_SIGNUP डिफ़ॉल्ट रूप से false)। एक शिकार email को invite करें, dummy password के साथ unauthenticated accept करें, फिर header-switch करके Victim Corp पर जाएँ। Invoices, API keys, Stripe, wallets।
यह कोई बिना खाते वाला random visitor नहीं है। यह password reset नहीं है। Dummy login विफल होता है। असली शिकार का password अभी भी काम करता है। Google/Okta accept IdP email को bind करता है और यह इस bug में नहीं आता। Single-org self-host जिसमें DB में किसी और का email नहीं है, आपको दूसरा merchant नहीं देता। एक ही database, दो orgs, एक ही email: यह देता है।
Stripe one-time invoice IDOR के समान tree। अलग bug।
मैंने Accept पढ़ा (कोई AuthenticableApiUser नहीं), फिर find_or_initialize_by(email:), फिर memberships-present branch जो password को skip करता है, फिर AuthToken.encode(user:)। फिर Google accept, जो हाँ google_oidc["email"] == invite.email की जाँच करता है। Email/password accept किसी मौजूदा उपयोगकर्ता के password को bind नहीं करता।
पहला client जो इसे देखेगा, एक अप्रयुक्त address को invite करेगा, इसे accept करेगा, और उनके द्वारा पोस्ट किए गए password के साथ एक बिल्कुल नया Lago उपयोगकर्ता प्राप्त करेगा। यही feature है। गवाह मौजूदा शिकार उपयोगकर्ता id का वापस आना है।
पहले से दर्ज गलत रास्ते: Google/Okta के साथ accept करना; createInvite 401 को बिना JWT के पूरी कहानी मानना (create authenticated है, accept नहीं); currentUser पर केवल attacker org को देखना (संकेत सूची में Victim Corp है); dummy password से login होना (इसका मतलब होगा कि hash overwrite हो गया - lab पुष्टि करता है कि ऐसा नहीं हुआ); एक reverse shell। नाटक। कोई inbox नहीं।
फिर: दो registerUser calls Victim Corp और Attacker Corp को plant करते हैं। हर run में नए emails ताकि गंदा volume user_already_exists 422 न दे। शिकार email पर createInvite। Dummy password के साथ unauthenticated acceptInvite। लौटाया गया user id शिकार से मेल खाता है। currentUser.organizations दोनों को सूचीबद्ध करता है। Header switch Victim Corp लौटाता है।
cd lab
./run.sh
लक्ष्य केवल http://127.0.0.1:13000। Stripe IDOR ने 13001 का उपयोग किया। कोई 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
यदि उपयोगकर्ता पहले से मौजूद है, तो membership संलग्न करने से पहले आवश्यकता होनी चाहिए कि वे स्वयं के रूप में authenticate करें (या mailbox / IdP साबित करें)। Unauthenticated accept से किसी मौजूदा उपयोगकर्ता के लिए JWT न बनाएँ। यदि invite token एक capability है तो inviter को इसे लौटाना बंद करें।
accept.rb · accept_service.rb · users_service.rb · create.rb · object.rb · google_service.rbGNU Affero GPL v3.0। देखें LICENSE। केवल Loopback lab। कोई warranty नहीं।