
Proof-of-Concept und Lab für eine Hyperswitch-Payout-Confirm-Schwachstelle: Ein Dummy-client_secret zusammen mit einem Betrag in POST /payouts/{id}/confirm erhöht den gespeicherten Auszahlungsbetrag.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-payout-confirm-secret
Hyperswitch 2026.09.21.0 - Juspay
payouts_confirm authentifiziert pk_ mit check_value_present("client_secret"). Vorhanden. Nicht gleich. Payments confirm bindet weiterhin: authenticate_client_secret gibt IR_09 bei Abweichung zurück. Der confirm-Body ist eine vollständige PayoutCreateRequest. update_payouts_and_payout_attempt schreibt amount vor dem Connector-Routing.
Dummy client_secret plus amount=99900 bei payouts confirm speichert 99900. Payments confirm mit demselben Dummy ist weiterhin IR_09.
| ID | noch keine CVE |
| CWE | CWE-863, CWE-345 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N |
| Produkt | Hyperswitch |
| Betroffen | bis 2026.09.21.0 payouts confirm |
| Auth | publishable pk_ plus payout id; Dummy-Secret |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Benötigt den publishable Key (öffentlich, Checkout) und die payout id (Payout-Link, Merchant-Dashboard, Client-Create-Response). POST /payouts/{id}/confirm mit einem Dummy-Secret und einem neuen amount. Retrieve zeigt den erhöhten Betrag, selbst wenn das Connector-Routing 400 IR_39 zurückgibt (kein geeigneter Payout-Connector). Der Schreibvorgang ist bereits erfolgt. Die Ausführung in einem Shop, der einen Payout-Prozessor hat, ist der nächste Schritt.
Gast ohne pk_ ist nicht dieser Bug. Secret sk_ confirm ist merchant-seitig. Keine Shell. Kein erfolgreicher Connector-Payout auf diesem Lab-Image.
Dasselbe Produkt wie der unsigned Worldpayxml webhook. Anderer Bug.
Ich habe check_value_present gelesen, dann payments req_cs != pi_cs, dann update_payouts_and_payout_attempt vor payouts_core. Auth für pk_ ist "checkout SDK." Das Secret soll die Browser-Session an diesen Payout binden. Payments macht Gleichheit. Payouts macht Vorhandensein. Ein nicht-leerer Dummy reicht aus. Das JSON ist weiterhin eine Create-Request, also ist amount bei confirm kein No-Op.
Der erste Client, der sich das ansieht, wird confirm ohne client_secret senden und MissingRequiredField erhalten. Vorhanden. Sende das echte Secret und du bist der Zahler. Sende einen Dummy bei payments confirm und du erhältst IR_09. Das ist die Kontrolle. Sende den Dummy bei payouts confirm.
Bereits dokumentierte Irrwege: HTTP 400 IR_39 als "nichts passiert" zu behandeln (den Payout retrieven); amount bleibt 1000, weil confirm amount weggelassen hat (unwrap_or behält den alten Wert); Payout bereits terminal (Success / Failed / Cancelled); eine Reverse Shell. Theater. Der Zeuge ist retrieve amount=99900 nach Dummy-Secret, während payments confirm weiterhin IR_09 ist.
Dann: einen Merchant anlegen, einen Secret Key, einen publishable pk_, einen worldpayxml payout Connector und POST /payouts/create mit confirm=false amount 1000. RequiresConfirmation. Payments Dummy-Secret-Kontrolle. Payouts Dummy 99900. Retrieve.
cd lab
./run.sh
Ziel nur http://127.0.0.1:18083. Kein gemeinsames Compose-Projekt mit dem Webhook-Lab verwenden.
IOC amount_before=1000 status_before=requires_confirmation
IOC payments.confirm dummy-secret status=400 IR_09 client_secret mismatch
IOC payouts.confirm dummy-secret status=400 IR_39 no eligible connector
IOC retrieve amount=99900 status=requires_confirmation
SUCCESS Hyperswitch payout confirm unbound client_secret amount raise
Vergleiche client_secret mit payouts.client_secret so wie payments es tut, und wende PayoutCreateRequest.amount bei confirm erst an, wenn diese Prüfung bestanden ist. Dummy-Secret muss IR_09 (oder 401) sein, und der Retrieve-Betrag muss 1000 bleiben.
payouts.rs routes · authentication.rs · payouts/helpers.rs · core/payouts.rs · payments/helpers.rsGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.