
Prueba de concepto y laboratorio para un fallo de confirmación de pagos de Hyperswitch: un client_secret ficticio junto con el importe en POST /payouts/{id}/confirm aumenta el importe de pago almacenado.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-payout-confirm-secret
Hyperswitch 2026.09.21.0 - Juspay
payouts_confirm autentica pk_ con check_value_present("client_secret"). Presente. No igual. Payments confirm todavía vincula: authenticate_client_secret devuelve IR_09 en caso de discrepancia. El cuerpo de confirm es un PayoutCreateRequest completo. update_payouts_and_payout_attempt escribe amount antes del enrutamiento al conector.
Un client_secret ficticio más amount=99900 en payouts confirm almacena 99900. Payments confirm con el mismo ficticio sigue siendo IR_09.
| ID | sin CVE todavía |
| CWE | CWE-863, CWE-345 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N |
| Producto | Hyperswitch |
| Afectado | hasta 2026.09.21.0 payouts confirm |
| Auth | pk_ publicable más id de payout; secreto ficticio |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Necesita la clave publicable (pública, checkout) y el id del payout (enlace de payout, panel del comerciante, respuesta de creación del cliente). POST /payouts/{id}/confirm con un secreto ficticio y un nuevo amount. La recuperación muestra el importe elevado incluso cuando el enrutamiento al conector devuelve 400 IR_39 (sin conector de payout elegible). La escritura ya ocurrió. La ejecución en una tienda que sí tiene un procesador de payouts es el siguiente paso.
Un invitado sin pk_ no es este bug. El confirm con secreto sk_ es del lado del comerciante. No es un shell. No es un payout exitoso a través del conector en esta imagen de laboratorio.
El mismo producto que el webhook Worldpayxml sin firma. Bug diferente.
Leí check_value_present, luego payments req_cs != pi_cs, luego update_payouts_and_payout_attempt antes de payouts_core. La autenticación para pk_ es "checkout SDK". Se supone que el secreto vincula la sesión del navegador a este payout. Payments hace igualdad. Payouts hace presencia. Un ficticio no vacío es suficiente. El JSON sigue siendo una solicitud de creación, así que amount no es un no-op en confirm.
El primer cliente que mire esto enviará confirm sin client_secret y obtendrá MissingRequiredField. Presente. Envía el secreto real y eres el pagador. Envía un ficticio en confirm de payments y obtienes IR_09. Ese es el control. Envía el ficticio en confirm de payouts.
Caminos equivocados ya registrados: tratar HTTP 400 IR_39 como "no pasó nada" (recupera el payout); el importe se queda en 1000 porque confirm omitió amount (unwrap_or mantiene el valor antiguo); payout ya terminal (Success / Failed / Cancelled); una reverse shell. Teatro. El testigo es recuperar amount=99900 tras el secreto ficticio, con payments confirm todavía IR_09.
Luego: crea un comerciante, una clave secreta, un pk_ publicable, un conector de payout worldpayxml, y POST /payouts/create con confirm=false amount 1000. RequiresConfirmation. Control de payments con secreto ficticio. Payouts ficticio 99900. Recuperar.
cd lab
./run.sh
Objetivo solo http://127.0.0.1:18083. No compartas un proyecto compose con el laboratorio del webhook.
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
Compara client_secret con payouts.client_secret de la misma forma que lo hace payments, y no apliques PayoutCreateRequest.amount en confirm hasta que esa comprobación pase. El secreto ficticio debe ser IR_09 (o 401), y el importe recuperado debe seguir siendo 1000.
payouts.rs · authentication.rs · payouts/helpers.rs · core/payouts.rs · payments/helpers.rsGNU Affero GPL v3.0. Ver LICENSE. Solo laboratorio en loopback. Sin garantía.