
Lago v1.53.0에서 Stripe 일회성 웹훅이 조직 범위 지정 없이 인보이스를 로드하여 교차 조직 결제 우회를 가능하게 하는 CWE-639 IDOR를 재현합니다. 루프백 랩을 포함합니다.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · lago-stripe-invoice-idor
Lago v1.53.0 - GetLago
POST /webhooks/stripe/:organization_id는 해당 URL 조직의 웹훅 시크릿으로 Stripe-Signature를 검증합니다. 그런 다음 StripeService#update_payment_status의 일회성 분기가 Invoice.find_by(id:)로 인보이스를 로드하며 organization_id가 없습니다. handle_missing_payment는 스코프가 적용되어 있습니다. Adyen 인보이스는 이미 organization_id:를 전달합니다. Cashfree와 Flutterwave는 스코프가 없는 Stripe의 find_by(id:)를 복사했습니다. 이 랩은 Stripe입니다.
같은 데이터베이스에 있는 다른 테넌트가 Stripe를 연결한 상태라면, 당신의 인보이스를 결제 완료로 표시할 수 있습니다. 그들의 Stripe 대시보드에는 일치하는 청구가 없습니다. 당신의 대시보드에는 있을지도 모릅니다.
| 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 일회성 웹훅; 공유 데이터베이스 |
| 인증 | Stripe가 연결된 공격자 조직; 피해자 인보이스 UUID |
| 라이선스 | GNU Affero GPL v3.0 |
| 랩 | 127.0.0.1 전용 |
자신의 조직으로 payment_intent.succeeded에 서명하고, 당신의 인보이스의 lago_invoice_id와 payment_type=one-time을 넣습니다. 스코프가 적용된 경로는 절대 실행되지 않습니다. 피해자 행은 payment_status=succeeded가 됩니다. 지갑 충전 / 결제 게이트 구독이 해제됩니다.
인증되지 않은 공격이 아닙니다. 셸도 아닙니다. 단일 조직 셀프 호스팅은 이 버그가 아닙니다. 그 서명 시크릿은 이미 해당 조직의 지급 채무를 소유하고 있기 때문입니다. 피해자 인보이스 UUID(포털 URL, 이메일, PDF, 유출된 API)와 피해자의 stripe_customer 행이 필요합니다. 더미 이벤트입니다. 실제 Stripe는 필요 없습니다.
acceptInvite ATO와 같은 트리입니다. 다른 버그입니다.
update_payment_status를 읽고, 그다음 일회성 create_payment의 find_by(id:)를, 그다음 handle_missing_payment의 organization_id:를 읽었습니다. 스코프가 적용된 경로의 주석은 조직 간에 동일한 Stripe 시크릿이 사용되는 것을 두려워합니다. 일회성 분기는 두려워하는 것을 잊었습니다.
이것을 처음 보는 클라이언트는 payment_type 없이 payment_intent.succeeded를 POST할 것입니다. handle_missing_payment는 인보이스 와 organization_id를 조회합니다. 공격자 조직, 피해자 인보이스. nil. Pending. 이것이 대조군입니다. 랩에서 이를 검증합니다.
이미 기록된 잘못된 방향들: 피해자 웹훅 시크릿으로 서명하기(그러면 이미 해당 판매자의 지급 채무를 소유하게 됨); 단일 조직 셀프 호스팅; 공격자 조직의 인보이스 UUID(조직 내 결제); 피해자에 stripe_customer 없음(예외이며 succeeded가 아님); 웹훅 HTTP 200을 오라클로 취급하기(작업은 비동기 - GET /api/v1/invoices/:id를 폴링); 리버스 셸. 연극입니다.
그다음: 두 번의 registerUser 호출로 Victim Corp과 Attacker Corp을 심습니다. 피해자 REST 키로 애드온, 고객, 그리고 skip_psp가 있는 미결제 인보이스를 생성하여 실제 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
일회성 경로에서 Invoice.find_by(id:, organization_id:)를 사용하는 것입니다. handle_missing_payment와 Adyen 인보이스가 이미 그렇게 하고 있습니다. Cashfree/Flutterwave도 마찬가지입니다.
webhooks_controller.rb · stripe_service.rb · adyen_service.rb · cashfree_service.rb · flutterwave_service.rbGNU Affero GPL v3.0. LICENSE를 참조하세요. 루프백 랩 전용. 보증 없음.