
يعيد إنتاج ثغرة CWE-639 IDOR في Lago v1.53.0 حيث يقوم webhook لمرة واحدة الخاص بـ Stripe بتحميل الفواتير دون تحديد نطاق المؤسسة، مما يتيح تجاوز الدفع عبر المؤسسات. يتضمن مختبر loopback.
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 باستخدام سر webhook الخاص بـ منظمة ذلك URL. ثم يقوم الفرع لمرة واحدة في StripeService#update_payment_status بتحميل الفاتورة باستخدام Invoice.find_by(id:) وبدون organization_id. بينما handle_missing_payment مُقيَّد بالنطاق. فواتير Adyen تمرر بالفعل organization_id:. بينما Cashfree وFlutterwave تنسخان find_by(id:) غير المُقيَّد من Stripe. هذا المختبر مخصص لـ 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 webhook لمرة واحدة من Stripe؛ قاعدة بيانات مشتركة |
| المصادقة | منظمة المهاجم مع ربط Stripe؛ UUID فاتورة الضحية |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1 فقط |
توقيع حدث payment_intent.succeeded باسم منظمته، مع وضع payment_type=one-time وlago_invoice_id الخاص بـ فاتورتك. المسار المُقيَّد بالنطاق لا يُنفَّذ أبدًا. يصبح صف الضحية payment_status=succeeded. يتم فتح شحن المحفظة / الاشتراك المقيَّد بالدفع.
ليس بدون مصادقة. ليس shell. الاستضافة الذاتية لمنظمة واحدة ليست هذا الخطأ: سر التوقيع هذا يملك بالفعل مدفوعات تلك المنظمة. تحتاج إلى UUID فاتورة الضحية (رابط البوابة، البريد الإلكتروني، PDF، API مسرّب) وصف stripe_customer للضحية. حدث وهمي. لا حاجة لـ Stripe حي.
نفس الشجرة مثل acceptInvite ATO. خطأ مختلف.
قرأت update_payment_status، ثم find_by(id:) لمرة واحدة في create_payment، ثم organization_id: في handle_missing_payment. التعليق على المسار المُقيَّد بالنطاق يخشى نفس سر Stripe المستخدم عبر المنظمات. لكن الفرع لمرة واحدة نسي أن يخاف.
أول عميل ينظر إلى هذا سيرسل POST لحدث payment_intent.succeeded بدون payment_type. يبحث handle_missing_payment عن الفاتورة و organization_id. منظمة المهاجم، فاتورة الضحية. nil. معلّق. هذا هو الضابط. المختبر يؤكد ذلك.
المنعطفات الخاطئة المسجلة بالفعل: التوقيع باستخدام سر webhook الخاص بـ الضحية (عندها تملك بالفعل مدفوعات ذلك التاجر)؛ الاستضافة الذاتية لمنظمة واحدة؛ UUID الفاتورة من منظمة المهاجم (دفع داخل المنظمة)؛ عدم وجود stripe_customer على الضحية (استثناء، وليس succeeded)؛ اعتبار HTTP 200 لـ webhook هو الدليل (المهمة غير متزامنة - استعلم عبر GET /api/v1/invoices/:id)؛ reverse shell. مسرحية.
ثم: استدعاءان لـ registerUser يزرعان Victim Corp وAttacker Corp. مفتاح REST الخاص بالضحية ينشئ إضافة، وعميلًا، وفاتورة غير مدفوعة مع skip_psp حتى لا أحتاج إلى PSP حي لإنشاء الصف. seed_stripe.rb يزرع مزودي Stripe وstripe_customer على الضحية. webhook_secret وهمي. sk_test_lab_* وهمي. حدث تحكم موقّع، بدون payment_type: لا يزال معلّقًا. حدث موقّع مع payment_type=one-time: succeeded، paid=1000.
cd lab
./run.sh
الهدف فقط http://127.0.0.1:13001. استخدم acceptInvite 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. مختبر loopback فقط. بدون ضمان.