
Proof-of-concept и лабораторная работа для уязвимости подтверждения выплат Hyperswitch: фиктивный client_secret вместе с суммой в POST /payouts/{id}/confirm увеличивает сохранённую сумму выплаты.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · hyperswitch-payout-confirm-secret
Hyperswitch 2026.09.21.0 - Juspay
payouts_confirm аутентифицирует pk_ с помощью check_value_present("client_secret"). Присутствует. Не равен. Payments confirm по-прежнему связывает: authenticate_client_secret возвращает IR_09 при несовпадении. Тело confirm — это полный PayoutCreateRequest. update_payouts_and_payout_attempt записывает amount до маршрутизации коннектора.
Фиктивный client_secret плюс amount=99900 при payouts confirm сохраняет 99900. Payments confirm с тем же фиктивным значением по-прежнему даёт IR_09.
| ID | пока нет 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 |
| Продукт | Hyperswitch |
| Затронуто | вплоть до 2026.09.21.0 payouts confirm |
| Аутентификация | публикуемый pk_ плюс payout id; фиктивный секрет |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1 |
Нужен публикуемый ключ (публичный, checkout) и payout id (ссылка на выплату, панель мерчанта, ответ клиента на создание). POST /payouts/{id}/confirm с фиктивным секретом и новым amount. Retrieve показывает увеличенную сумму, даже когда маршрутизация коннектора возвращает 400 IR_39 (нет подходящего payout-коннектора). Запись уже произошла. Выполнение на магазине, у которого есть payout-процессор, — следующий шаг.
Гость без pk_ — это не данная уязвимость. Confirm с секретом sk_ — сторона мерчанта. Не шелл. Не успешная выплата через коннектор на этом образе лаборатории.
Тот же продукт, что и unsigned Worldpayxml webhook. Другая уязвимость.
Я прочитал check_value_present, затем payments req_cs != pi_cs, затем update_payouts_and_payout_attempt перед payouts_core. Аутентификация для pk_ — это "checkout SDK". Секрет должен привязывать сессию браузера к этой выплате. Payments выполняет сравнение на равенство. Payouts — только проверку наличия. Непустого фиктивного значения достаточно. JSON по-прежнему является запросом на создание, поэтому amount не является no-op при confirm.
Первый клиент, который посмотрит на это, отправит confirm без client_secret и получит MissingRequiredField. Присутствует. Отправьте настоящий секрет — и вы плательщик. Отправьте фиктивный при payments confirm — и получите IR_09. Это контроль. Отправьте фиктивный при payouts confirm.
Уже зафиксированные неверные повороты: трактовка HTTP 400 IR_39 как "ничего не произошло" (сделайте retrieve выплаты); amount остаётся 1000, потому что confirm опустил amount (unwrap_or сохраняет старое значение); выплата уже в терминальном состоянии (Success / Failed / Cancelled); reverse shell. Театр. Свидетельство — retrieve amount=99900 после фиктивного секрета, при том что payments confirm по-прежнему IR_09.
Затем: создайте мерчанта, секретный ключ, публикуемый pk_, коннектор worldpayxml payout и POST /payouts/create с confirm=false amount 1000. RequiresConfirmation. Контроль с фиктивным секретом на payments. Фиктивный 99900 на payouts. Retrieve.
cd lab
./run.sh
Цель только http://127.0.0.1:18083. Не делите compose-проект с лабораторией 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
Сравнивайте client_secret с payouts.client_secret так, как это делает payments, и не применяйте PayoutCreateRequest.amount при confirm, пока эта проверка не пройдёт. Фиктивный секрет должен давать IR_09 (или 401), а amount при retrieve должен оставаться 1000.
payouts.rs routes · authentication.rs · payouts/helpers.rs · core/payouts.rs · payments/helpers.rsGNU Affero GPL v3.0. См. LICENSE. Только loopback-лаборатория. Без гарантий.