
Riproduce un IDOR CWE-639 in Lago v1.53.0 dove il webhook one-time di Stripe carica le fatture senza lo scoping dell'organizzazione, consentendo il bypass dei pagamenti tra organizzazioni. Include un laboratorio loopback.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-stripe-invoice-idor
Lago v1.53.0 - GetLago
POST /webhooks/stripe/:organization_id verifica Stripe-Signature con il webhook secret dell'organizzazione di quell'URL. Poi il ramo one-time in StripeService#update_payment_status carica la fattura con Invoice.find_by(id:) e senza organization_id. handle_missing_payment è scoped. Le fatture Adyen passano già organization_id:. Cashfree e Flutterwave copiano il find_by(id:) non scoped di Stripe. Questo lab è Stripe.
Un altro tenant sullo stesso database, con Stripe connesso, può contrassegnare la tua fattura come pagata. La loro dashboard Stripe non ha alcun addebito corrispondente. La tua potrebbe averlo.
| ID | nessuna CVE al momento |
| 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 |
| Prodotto | Lago / lago-api |
| Affetto | fino alla v1.53.0 webhook one-time Stripe; database condiviso |
| Auth | organizzazione attaccante con Stripe connesso; UUID della fattura vittima |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Firma un payment_intent.succeeded come la propria organizzazione, inserisci payment_type=one-time e il lago_invoice_id della tua fattura. Il percorso scoped non viene mai eseguito. La riga della vittima diventa payment_status=succeeded. Il top-up del wallet / l'abbonamento vincolato al pagamento si sblocca.
Non è non autenticato. Non è una shell. Il self-host con una sola organizzazione non è questo bug: quel signing secret possiede già i pagabili di quell'organizzazione. Ti serve l'UUID di una fattura vittima (URL del portale, email, PDF, API trapelata) e una riga stripe_customer della vittima. Evento fittizio. Nessun Stripe live.
Stesso albero dell'acceptInvite ATO. Bug diverso.
Ho letto update_payment_status, poi il find_by(id:) di create_payment one-time, poi l'organization_id: di handle_missing_payment. Il commento sul percorso scoped teme lo stesso Stripe secret usato tra organizzazioni. Il ramo one-time si è dimenticato di temerlo.
Il primo client che guarda questo farà una POST di un payment_intent.succeeded senza payment_type. handle_missing_payment cerca la fattura e organization_id. Organizzazione attaccante, fattura vittima. nil. Pending. Questo è il controllo. Il lab lo verifica.
Vicoli ciechi già registrati: firmare con il webhook secret della vittima (allora possiedi già i pagabili di quel merchant); self-host con una sola organizzazione; UUID della fattura dell'organizzazione attaccante (pagamento in-org); nessun stripe_customer sulla vittima (eccezione, non succeeded); trattare l'HTTP 200 del webhook come oracolo (il job è asincrono - fai polling su GET /api/v1/invoices/:id); una reverse shell. Teatro.
Poi: due chiamate registerUser piantano Victim Corp e Attacker Corp. La REST key della vittima crea un add-on, un customer e una fattura non pagata con skip_psp così non mi serviva un PSP live per creare la riga. seed_stripe.rb pianta i provider Stripe e uno stripe_customer sulla vittima. webhook_secret fittizio. sk_test_lab_* fittizio. Evento di controllo firmato, senza payment_type: ancora pending. Evento firmato con payment_type=one-time: succeeded, paid=1000.
cd lab
./run.sh
Target solo http://127.0.0.1:13001. Invite ATO usava 13000. Questo non condivide un volume con quel 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:) sul percorso one-time, come già fanno handle_missing_payment e le fatture Adyen. Lo stesso per Cashfree/Flutterwave.
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0. Vedi LICENSE. Solo lab su loopback. Nessuna garanzia.