
Lago v1.53.0 में CWE-639 IDOR को पुनरुत्पादित करता है, जहाँ Stripe one-time webhook संगठन स्कोपिंग के बिना invoices लोड करता है, जिससे cross-org payment bypass संभव हो जाता है। इसमें एक loopback lab शामिल है।
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-stripe-invoice-idor
Lago v1.53.0 - GetLago
POST /webhooks/stripe/:organization_id उस URL org के webhook secret के साथ Stripe-Signature को verify करता है। फिर StripeService#update_payment_status में one-time branch invoice को Invoice.find_by(id:) से load करता है और कोई organization_id नहीं होता। handle_missing_payment scoped है। Adyen invoices पहले से organization_id: पास करते हैं। Cashfree और Flutterwave unscoped Stripe find_by(id:) को copy करते हैं। यह lab Stripe है।
उसी database पर मौजूद कोई अन्य tenant, जिसके पास Stripe connected है, आपके invoice को paid mark कर सकता है। उनके Stripe dashboard में कोई matching charge नहीं होगा। आपके में हो सकता है।
| ID | अभी तक कोई CVE नहीं |
| CWE | CWE-639, CWE-345 |
| CVSS | High: 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N |
| Product | Lago / lago-api |
| Affected | v1.53.0 तक Stripe one-time webhook; shared database |
| Auth | attacker org जिसके पास Stripe connected है; victim invoice UUID |
| License | GNU Affero GPL v3.0 |
| Lab | केवल 127.0.0.1 |
अपने org के रूप में payment_intent.succeeded पर sign करें, आपके invoice का payment_type=one-time और lago_invoice_id डालें। Scoped path कभी run नहीं होता। Victim row payment_status=succeeded हो जाती है। Wallet top-up / payment-gated sub unlock हो जाता है।
यह unauthenticated नहीं है। Shell नहीं है। Single-org self-host यह bug नहीं है: वह signing secret पहले से उस org के payables का owner है। आपको एक victim invoice UUID चाहिए (portal URL, email, PDF, leaked API) और एक victim stripe_customer row। Dummy event। कोई live Stripe नहीं।
acceptInvite ATO के समान tree। अलग bug।
मैंने update_payment_status पढ़ा, फिर one-time create_payment का find_by(id:), फिर handle_missing_payment का organization_id:। Scoped path पर टिप्पणी उसी Stripe secret से डरती है जो orgs के across उपयोग होता है। One-time branch डरना भूल गया।
पहला client जो इसे देखेगा वह payment_type के बिना payment_intent.succeeded POST करेगा। handle_missing_payment invoice और organization_id दोनों को lookup करता है। Attacker org, victim invoice। nil। Pending। यही control है। Lab इसे assert करता है।
गलत रास्ते पहले से दर्ज हैं: victim webhook secret से sign करना (तब आप पहले से उस merchant के payables के owner हैं); single-org self-host; attacker org से invoice UUID (in-org payment); victim पर कोई stripe_customer नहीं (exception, succeeded नहीं); webhook HTTP 200 को oracle मानना (job async है - GET /api/v1/invoices/:id poll करें); reverse shell। Theatre।
फिर: दो registerUser calls Victim Corp और Attacker Corp plant करते हैं। Victim REST key एक add-on, एक customer, और skip_psp के साथ एक unpaid invoice बनाता है ताकि row mint करने के लिए मुझे live PSP की आवश्यकता न पड़े। seed_stripe.rb victim पर Stripe providers और एक stripe_customer plant करता है। Dummy webhook_secret। Dummy sk_test_lab_*। Signed control event, payment_type के बिना: अभी भी pending। payment_type=one-time के साथ signed event: succeeded, paid=1000।
cd lab
./run.sh
Target केवल http://127.0.0.1:13001। Invite ATO ने 13000 उपयोग किया। यह उस lab के साथ volume share नहीं करता।
IOC payment_status_before=pending
IOC control webhook (no payment_type) payment_status_after_control=pending
IOC attack webhook payment_type=one-time
IOC poll payment_status=succeeded paid=1000
SUCCESS Lago Stripe one-time webhook unscoped invoice
One-time path पर Invoice.find_by(id:, organization_id:), जैसे handle_missing_payment और Adyen invoices पहले से करते हैं। Cashfree/Flutterwave के लिए भी वही।
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0। LICENSE देखें। केवल Loopback lab। कोई warranty नहीं।