
Reproduziert eine CWE-639-IDOR in Lago v1.53.0, bei der der Stripe-Einmal-Webhook Rechnungen ohne Organisations-Scoping lädt, was eine organisationsübergreifende Zahlungsumgehung ermöglicht. Enthält ein 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 verifiziert Stripe-Signature mit dem Webhook-Secret der Organisation aus dieser URL. Dann lädt der One-Time-Zweig in StripeService#update_payment_status die Rechnung mit Invoice.find_by(id:) und ohne organization_id. handle_missing_payment ist eingegrenzt. Adyen-Rechnungen übergeben bereits organization_id:. Cashfree und Flutterwave kopieren das nicht eingegrenzte Stripe find_by(id:). Dieses Lab ist Stripe.
Ein anderer Tenant auf derselben Datenbank, mit verbundenem Stripe, kann deine Rechnung als bezahlt markieren. Dessen Stripe-Dashboard hat keine passende Abbuchung. Deines vielleicht schon.
| ID | noch keine 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 |
| Produkt | Lago / lago-api |
| Betroffen | bis v1.53.0 Stripe One-Time-Webhook; geteilte Datenbank |
| Auth | Angreifer-Org mit verbundenem Stripe; UUID der Opfer-Rechnung |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Signiere ein payment_intent.succeeded als seine Org, setze payment_type=one-time und lago_invoice_id von deiner Rechnung. Der eingegrenzte Pfad läuft nie. Die Opfer-Zeile wird payment_status=succeeded. Wallet-Aufladung / zahlungsgeschütztes Abo wird freigeschaltet.
Nicht unauthentifiziert. Keine Shell. Single-Org-Self-Host ist nicht dieser Bug: dieses Signing-Secret besitzt bereits die Zahlungsverbindlichkeiten dieser Org. Du brauchst eine UUID der Opfer-Rechnung (Portal-URL, E-Mail, PDF, geleakte API) und eine stripe_customer-Zeile des Opfers. Dummy-Event. Kein Live-Stripe.
Derselbe Tree wie der acceptInvite ATO. Anderer Bug.
Ich las update_payment_status, dann das One-Time-create_payment find_by(id:), dann das organization_id: von handle_missing_payment. Der Kommentar auf dem eingegrenzten Pfad fürchtet dasselbe Stripe-Secret, das über Orgs hinweg verwendet wird. Der One-Time-Zweig hat vergessen, sich zu fürchten.
Der erste Client, der sich das ansieht, wird ein payment_intent.succeeded ohne payment_type POSTen. handle_missing_payment sucht die Rechnung und organization_id. Angreifer-Org, Opfer-Rechnung. nil. Pending. Das ist die Kontrolle. Das Lab bestätigt sie.
Fehlversuche bereits festgehalten: Signieren mit dem Webhook-Secret des Opfers (dann besitzt du bereits die Zahlungsverbindlichkeiten dieses Merchants); Single-Org-Self-Host; Rechnungs-UUID aus der Angreifer-Org (In-Org-Zahlung); kein stripe_customer beim Opfer (Exception, nicht succeeded); Webhook-HTTP-200 als Orakel behandeln (der Job ist asynchron - poll GET /api/v1/invoices/:id); eine Reverse Shell. Theater.
Dann: zwei registerUser-Aufrufe legen Victim Corp und Attacker Corp an. Der Victim-REST-Key erstellt ein Add-on, einen Kunden und eine unbezahlte Rechnung mit skip_psp, sodass ich kein Live-PSP brauchte, um die Zeile zu erzeugen. seed_stripe.rb legt Stripe-Provider und einen stripe_customer beim Opfer an. Dummy-webhook_secret. Dummy-sk_test_lab_*. Signiertes Kontroll-Event, kein payment_type: weiterhin pending. Signiertes Event mit payment_type=one-time: succeeded, paid=1000.
cd lab
./run.sh
Ziel nur http://127.0.0.1:13001. Invite ATO verwendete 13000. Dieses teilt kein Volume mit jenem 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:) auf dem One-Time-Pfad, so wie es handle_missing_payment und Adyen-Rechnungen bereits tun. Dasselbe für Cashfree/Flutterwave.
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.