Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
lago-stripe-invoice-idor — Reproduces a CWE-639 IDOR in Lago v1.53.0 where the Stripe one-time webhook loads invoices without organization scoping, enabling cross-org payment bypass. Includes a loopback lab. | Kitploit
Tools/GitHubGitHub/abraxas/lago-stripe-invoice-idor
Authentication & AuthorizationVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingAPI SecurityLabs & Practice

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
GitHub
abraxas/lago-stripe-invoice-idor

lago-stripe-invoice-idor

Reproduces a CWE-639 IDOR in Lago v1.53.0 where the Stripe one-time webhook loads invoices without organization scoping, enabling cross-org payment bypass. Includes a loopback lab.

View Repository
1018 days agoNot yet reviewed

Abraxas Labs - lago-stripe-invoice-idor

abraxaslabs.tech  ·  github.com/abraxas  ·  @abraxas_null  ·  [email protected]  ·  lago-stripe-invoice-idor

lago-stripe-invoice-idor

Lago v1.53.0 - GetLago

POST /webhooks/stripe/:organization_id verifies Stripe-Signature with that URL org's webhook secret. Then the one-time branch in StripeService#update_payment_status loads the invoice with Invoice.find_by(id:) and no organization_id. handle_missing_payment is scoped. Adyen invoices already pass organization_id:. Cashfree and Flutterwave copy the unscoped Stripe find_by(id:). This lab is Stripe.

Another tenant on the same database, with Stripe connected, can mark your invoice paid. Their Stripe dashboard has no matching charge. Yours might.

IDno CVE yet
CWECWE-639, CWE-345
CVSSHigh: 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:N
ProductLago / lago-api
Affectedthrough v1.53.0 Stripe one-time webhook; shared database
Authattacker org with Stripe connected; victim invoice UUID
LicenseGNU Affero GPL v3.0
Lab127.0.0.1 only

What an attacker can do

Sign a payment_intent.succeeded as their org, put payment_type=one-time and lago_invoice_id of your invoice. The scoped path never runs. The victim row becomes payment_status=succeeded. Wallet top-up / payment-gated sub unlocks.

Not unauthenticated. Not a shell. Single-org self-host is not this bug: that signing secret already owns that org's payables. You need a victim invoice UUID (portal URL, email, PDF, leaked API) and a victim stripe_customer row. Dummy event. No live Stripe.

Same tree as the acceptInvite ATO. Different bug.

How I found it

I read update_payment_status, then the one-time create_payment find_by(id:), then handle_missing_payment's organization_id:. The comment on the scoped path is afraid of the same Stripe secret used across orgs. The one-time branch forgot to be afraid.

The first client that looks at this will POST a payment_intent.succeeded without payment_type. handle_missing_payment looks up the invoice and organization_id. Attacker org, victim invoice. nil. Pending. That is the control. Lab asserts it.

Wrong turns already recorded: signing with the victim webhook secret (then you already own that merchant's payables); single-org self-host; invoice UUID from the attacker org (in-org payment); no stripe_customer on the victim (exception, not succeeded); treating webhook HTTP 200 as the oracle (the job is async - poll GET /api/v1/invoices/:id); a reverse shell. Theatre.

Then: two registerUser calls plant Victim Corp and Attacker Corp. Victim REST key creates an add-on, a customer, and an unpaid invoice with skip_psp so I did not need a live PSP to mint the row. seed_stripe.rb plants Stripe providers and a stripe_customer on the victim. Dummy webhook_secret. Dummy sk_test_lab_*. Signed control event, no payment_type: still pending. Signed event with payment_type=one-time: succeeded, paid=1000.

Lab

cd lab
./run.sh

Target only http://127.0.0.1:13001. Invite ATO used 13000. This one does not share a volume with that 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

The fix

Invoice.find_by(id:, organization_id:) on the one-time path, the way handle_missing_payment and Adyen invoices already do. Same for Cashfree/Flutterwave.

References

  • github.com/getlago/lago tag v1.53.0 · lago-api
  • webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rb
  • Same product: lago-invite-ato
  • CWE-639 · CWE-345

License

GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.

Download Tool