
Prova de conceito e laboratório para uma falha de confirmação de payout do Hyperswitch: um client_secret fictício mais o valor em POST /payouts/{id}/confirm aumenta o valor armazenado do payout.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-payout-confirm-secret
Hyperswitch 2026.09.21.0 - Juspay
payouts_confirm autentica pk_ com check_value_present("client_secret"). Presente. Não igual. O confirm de payments ainda vincula: authenticate_client_secret retorna IR_09 em caso de divergência. O corpo do confirm é um PayoutCreateRequest completo. update_payouts_and_payout_attempt grava amount antes do roteamento do conector.
client_secret fictício mais amount=99900 no confirm de payouts armazena 99900. O confirm de payments com o mesmo fictício ainda é IR_09.
| ID | sem CVE ainda |
| 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 |
| Produto | Hyperswitch |
| Afetado | até o confirm de payouts 2026.09.21.0 |
| Autenticação | pk_ publicável mais id de payout; segredo fictício |
| Licença | GNU Affero GPL v3.0 |
| Lab | somente 127.0.0.1 |
Precisa da chave publicável (pública, checkout) e do id do payout (link de payout, painel do comerciante, resposta de criação do cliente). POST /payouts/{id}/confirm com um segredo fictício e um novo amount. O retrieve mostra o valor elevado mesmo quando o roteamento do conector retorna 400 IR_39 (nenhum conector de payout elegível). A gravação já aconteceu. O cumprimento em uma loja que tem um processador de payout é o próximo passo.
Convidado sem pk_ não é este bug. O confirm com sk_ secreto é do lado do comerciante. Não é um shell. Não é um payout de conector bem-sucedido nesta imagem de lab.
Mesmo produto do webhook Worldpayxml não assinado. Bug diferente.
Li check_value_present, depois payments req_cs != pi_cs, depois update_payouts_and_payout_attempt antes de payouts_core. A autenticação para pk_ é "checkout SDK". O segredo deveria vincular a sessão do navegador a este payout. Payments faz igualdade. Payouts faz presença. Um fictício não vazio é suficiente. O JSON ainda é uma requisição de criação, então amount não é um no-op no confirm.
O primeiro cliente que olhar para isso enviará confirm sem client_secret e receberá MissingRequiredField. Presente. Envie o segredo real e você é o pagador. Envie um fictício no confirm de payments e você recebe IR_09. Esse é o controle. Envie o fictício no confirm de payouts.
Caminhos errados já registrados: tratar HTTP 400 IR_39 como "nada aconteceu" (faça o retrieve do payout); o amount permanece 1000 porque o confirm omitiu amount (unwrap_or mantém o valor antigo); payout já terminal (Success / Failed / Cancelled); um reverse shell. Teatro. A testemunha é o retrieve amount=99900 após segredo fictício, com o confirm de payments ainda IR_09.
Então: crie um comerciante, uma chave secreta, um pk_ publicável, um conector de payout worldpayxml, e POST /payouts/create com confirm=false amount 1000. RequiresConfirmation. Controle de segredo fictício em payments. Payouts fictício 99900. Retrieve.
cd lab
./run.sh
Alvo somente http://127.0.0.1:18083. Não compartilhe um projeto compose com o lab do 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
Compare client_secret com payouts.client_secret da mesma forma que payments faz, e não aplique PayoutCreateRequest.amount no confirm até que essa verificação passe. O segredo fictício deve ser IR_09 (ou 401), e o amount do retrieve deve permanecer 1000.
payouts.rs · authentication.rs · payouts/helpers.rs · core/payouts.rs · payments/helpers.rsGNU Affero GPL v3.0. Veja LICENSE. Somente lab em loopback. Sem garantia.