
Reproduit un IDOR CWE-639 dans Lago v1.53.0 où le webhook one-time de Stripe charge les factures sans restriction par organisation, permettant un contournement de paiement inter-organisations. Inclut un lab en boucle locale.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-stripe-invoice-idor
Lago v1.53.0 - GetLago
POST /webhooks/stripe/:organization_id vérifie Stripe-Signature avec le secret de webhook de cette organisation d'URL. Ensuite, la branche one-time dans StripeService#update_payment_status charge la facture avec Invoice.find_by(id:) et sans organization_id. handle_missing_payment est bien scopé. Les factures Adyen passent déjà organization_id:. Cashfree et Flutterwave copient le find_by(id:) non scopé de Stripe. Ce lab concerne Stripe.
Un autre tenant sur la même base de données, avec Stripe connecté, peut marquer votre facture comme payée. Leur tableau de bord Stripe n'a aucun paiement correspondant. Le vôtre peut-être.
| ID | pas encore de CVE |
| CWE | CWE-639, CWE-345 |
| CVSS | Élevé : 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N |
| Produit | Lago / lago-api |
| Affecté | jusqu'à v1.53.0 webhook one-time Stripe ; base de données partagée |
| Auth | organisation attaquante avec Stripe connecté ; UUID de la facture victime |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Signer un payment_intent.succeeded en tant que leur organisation, mettre payment_type=one-time et lago_invoice_id de votre facture. Le chemin scopé ne s'exécute jamais. La ligne victime devient payment_status=succeeded. Le rechargement de portefeuille / l'abonnement conditionné au paiement se débloque.
Pas non authentifié. Pas un shell. Un self-host mono-organisation n'est pas ce bug : ce secret de signature possède déjà les payables de cette organisation. Il vous faut un UUID de facture victime (URL du portail, email, PDF, API fuitée) et une ligne stripe_customer victime. Événement factice. Pas de Stripe en production.
Même arbre que l'ATO acceptInvite. Bug différent.
J'ai lu update_payment_status, puis le find_by(id:) de create_payment one-time, puis le organization_id: de handle_missing_payment. Le commentaire sur le chemin scopé craint le même secret Stripe utilisé à travers les organisations. La branche one-time a oublié d'avoir peur.
Le premier client qui regarde ça va POSTer un payment_intent.succeeded sans payment_type. handle_missing_payment cherche la facture et organization_id. Organisation attaquante, facture victime. nil. En attente. C'est le contrôle. Le lab l'affirme.
Fausses pistes déjà consignées : signer avec le secret de webhook de la victime (là vous possédez déjà les payables de ce marchand) ; self-host mono-organisation ; UUID de facture de l'organisation attaquante (paiement intra-organisation) ; pas de stripe_customer sur la victime (exception, pas succeeded) ; traiter le HTTP 200 du webhook comme l'oracle (le job est asynchrone - poller GET /api/v1/invoices/:id) ; un reverse shell. Du théâtre.
Ensuite : deux appels registerUser plantent Victim Corp et Attacker Corp. La clé REST de la victime crée un add-on, un client, et une facture impayée avec skip_psp pour ne pas avoir besoin d'un PSP en production pour créer la ligne. seed_stripe.rb plante les providers Stripe et un stripe_customer sur la victime. webhook_secret factice. sk_test_lab_* factice. Événement de contrôle signé, sans payment_type : toujours en attente. Événement signé avec payment_type=one-time : succeeded, paid=1000.
cd lab
./run.sh
Cible uniquement http://127.0.0.1:13001. L'ATO acceptInvite utilisait 13000. Celui-ci ne partage pas de volume avec ce lab.
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
Invoice.find_by(id:, organization_id:) sur le chemin one-time, comme le font déjà handle_missing_payment et les factures Adyen. Idem pour Cashfree/Flutterwave.
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0. Voir LICENSE. Lab en loopback uniquement. Aucune garantie.