
إثبات مفهوم لثغرة Hyperswitch Worldpayxml webhook غير المصادق عليها (CWE-345/306) التي تزوّر XML بحالة SETTLED لتحديد المدفوعات كناجحة دون التقاط PSP.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-unsigned-webhook
المقال: https://abraxaslabs.tech/research/hyperswitch-unsigned-webhook
Hyperswitch 2026.09.21.0 - Juspay
Worldpayxml::verify_webhook_source تُرجع Ok(true) مع تعليق يفيد بأن التحقق يتم عبر mTLS. لا يوجد mTLS على هذا المسار. تتجاهل الدالة الجسم والترويسات. بعد ذلك تثق incoming.rs بـ source_verified وتأخذ HandleResponse دون PSync. تُترجم LastEvent::Settled إلى PaymentIntentSuccess. خوارزمية IncomingWebhook الافتراضية هي NoAlgorithm. ترث BitPay وShift4 ذلك. أما Worldpayxml فلا تصل إلى ذلك الحد أصلاً. إنها تقطع الطريق.
XML SETTLED وهمي غير موثّق يجعل الدفع ناجحًا. لا يوجد تحصيل من Worldpay. لا شهادة عميل.
| المعرّف | لا يوجد CVE بعد |
| CWE | CWE-345، CWE-306 |
| CVSS | عالٍ: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N |
| المنتج | Hyperswitch |
| المتأثر | حتى 2026.09.21.0 webhooks الواردة من Worldpayxml |
| المصادقة | غير موثّقة؛ تحتاج إلى merchant_id وorderCode |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1 فقط |
تحتاج إلى merchant_id (موجود في كل رابط من روابط لوحة تحكم Hyperswitch) وorderCode مساوٍ لـ connector_transaction_id الخاص بالمحاولة. أرسل POST إلى /webhooks/{merchant_id}/worldpayxml بـ SETTLED غير موقّع. لو كانت source_verified خاطئة، لكان incoming قد نفّذ Trigger وأجرى PSync للموصّل. لكن Worldpayxml لا تسلك هذا المسار أبدًا. تتحول النية إلى Charged. في المختبر: status=succeeded. XML وهمي. لا PSP.
تخمين معرّفات الدفع ليس هو الثغرة. تزوير SETTLED لمعرّف رأيته بالفعل هو الثغرة. ليس shell. وليس تسوية Worldpay حقيقية.
نفس المنتج، ثغرة مختلفة: payout confirm unbound client_secret.
قرأت Ok(true)، ثم HandleResponse مقابل Trigger، ثم Settled -> PaymentIntentSuccess. يقول التعليق إن هناك mTLS. لكن طلب POST في المختبر لا يحمل شهادة عميل.
أول عميل ينظر إلى هذا سيرسل POST للـ webhook مقابل requires_confirmation. لن يحوّل incoming دفعةً لم تخرج بعد من مرحلة التأكيد إلى Charged. اختم المحاولة، أو انتظر حتى requires_capture / processing. المختبر يقوم بالختم. ذلك الختم هو تجهيز مختبري. المحاولة الحية تملك بالفعل معرّف الموصّل. أما الـ webhook فلا يملكه.
مسارات خاطئة سُجّلت بالفعل: HMAC موقّع على موصّل يتحقق فعلاً (هذا المسار لا يفعل)؛ اعتبار HTTP 200 من الـ webhook هو الدليل (استعلم عبر GET /payments/{id} حتى يصبح status=succeeded)؛ orderCode ليس connector_transaction_id؛ reverse shell. مسرحيات.
ثم: تاجر، مفتاح API، MCA من worldpayxml، دفعة بـ confirm=false، اختم connector_transaction_id، SETTLED غير موقّع، استرجع.
cd lab
./run.sh
استهدف فقط http://127.0.0.1:18082. يقوم run.sh باستنساخ الوسم 2026.09.21.0 إلى lab/hyperswitch-src من أجل الإعدادات وعمليات الترحيل.
IOC status_before=requires_confirmation
IOC webhook status=200 unsigned lastEvent=SETTLED
IOC poll status=succeeded
SUCCESS Hyperswitch unsigned Worldpayxml webhook Charged
تحقّق من إشعار Worldpay (التوقيع / mTLS الذي ينتهي عنده المسار فعليًا). لا تستدعِ HandleResponse على موصل أعاد Ok(true) من عملية لا تفعل شيئًا. يجب ألا ينقل SETTLED غير الموقّع النية إلى حالة النجاح.
worldpayxml.rs · transformers.rs · incoming.rs · webhooks.rs · crypto.rsGNU Affero GPL v3.0. راجع LICENSE. مختبر loopback فقط. دون أي ضمان.