
Lago v1.53.0 における CWE-639 IDOR を再現します。Stripe のワンタイム Webhook が組織スコープなしで請求書を読み込むため、組織間の支払いバイパスが可能になります。ループバックラボを含みます。
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-stripe-invoice-idor
Lago v1.53.0 - GetLago
POST /webhooks/stripe/:organization_id は Stripe-Signature をその URL の組織の webhook secret で検証する。次に StripeService#update_payment_status 内の one-time 分岐が Invoice.find_by(id:) で invoice をロードし、organization_id は付けない。handle_missing_payment はスコープされている。Adyen の invoice はすでに organization_id: を渡している。Cashfree と Flutterwave はスコープされていない Stripe の find_by(id:) をコピーしている。このラボは Stripe を対象とする。
同じデータベース上の別テナントが、Stripe を接続していれば、あなたの invoice を支払い済みにできる。相手の Stripe ダッシュボードには対応する 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 |
| 製品 | Lago / lago-api |
| 影響範囲 | v1.53.0 までの Stripe one-time webhook、共有データベース |
| 認証 | Stripe を接続した攻撃者組織、被害者の invoice UUID |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
payment_intent.succeeded を自分の組織として署名し、あなたの invoice の lago_invoice_id と payment_type=one-time を入れる。スコープされたパスは決して実行されない。被害者の行は payment_status=succeeded になる。ウォレットのトップアップや支払いゲート付きサブスクリプションが解除される。
未認証ではない。シェルでもない。単一組織のセルフホストはこのバグではない。その署名 secret はすでにその組織の支払いを所有しているからだ。必要なのは被害者の invoice UUID(ポータル URL、メール、PDF、漏洩した API)と被害者の stripe_customer 行である。ダミーイベント。ライブ Stripe は不要。
acceptInvite ATO と同じツリー。別のバグ。
update_payment_status を読み、次に one-time の create_payment の find_by(id:) を読み、そして handle_missing_payment の organization_id: を読んだ。スコープされたパスに付いたコメントは、組織をまたいで同じ Stripe secret が使われることを恐れている。one-time 分岐は恐れることを忘れていた。
これを最初に見るクライアントは payment_type なしで payment_intent.succeeded を POST するだろう。handle_missing_payment は invoice と organization_id を照合する。攻撃者組織、被害者の invoice。nil。Pending。これがコントロールである。ラボはそれを検証する。
すでに記録済みの誤った道:被害者の webhook secret で署名する(それならすでにそのマーチャントの支払いを所有している);単一組織のセルフホスト;攻撃者組織の invoice UUID(組織内支払い);被害者に stripe_customer がない(例外であり succeeded ではない);webhook の HTTP 200 をオラクルとして扱う(ジョブは非同期 - GET /api/v1/invoices/:id をポーリングする);リバースシェル。茶番である。
次に:2 回の registerUser 呼び出しで Victim Corp と Attacker Corp を仕込む。被害者の REST キーで add-on、customer、そして skip_psp 付きの未払い invoice を作成し、行を生成するのにライブ PSP を必要としなかった。seed_stripe.rb は Stripe プロバイダーと被害者の stripe_customer を仕込む。ダミーの webhook_secret。ダミーの sk_test_lab_*。署名済みコントロールイベント、payment_type なし:依然 pending。payment_type=one-time 付きの署名済みイベント:succeeded、paid=1000。
cd lab
./run.sh
対象は必ず http://127.0.0.1:13001 のみ。Invite ATO は 13000 を使用していた。こちらはそのラボとボリュームを共有しない。
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 パスで Invoice.find_by(id:, organization_id:) を使う。handle_missing_payment と Adyen の invoice がすでにそうしているように。Cashfree/Flutterwave も同様に。
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0。LICENSE を参照。ループバックラボのみ。無保証。