• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Раскручиваем уязвимость в платежной логике

stihl

bot
Moderator
Регистрация
09.02.2012
Сообщения
1,381
Розыгрыши
0
Реакции
694
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
В этой статье я расскажу про кейс с Bug Bounty, в котором мне удалось обойти бизнес‑логику приложения, создавать валидные платежи и списывать деньги клиентов. Я тестировал API одной финансовой организации, у которой есть фонд с личным кабинетом. Основные функции были за авторизацией, за исключением кнопки «Сделать взнос»...
Это исследование получило второе место на Для просмотра ссылки Войди или Зарегистрируйся в категории «Hack the logic». Соревнование ежегодно проводится компанией Awillix.
Название кейса «Один шаг до крита» — не просто так. Я буквально поменял step=0 на step=1 и, даже не открывая Burp, получил два критичных для вендора бага: списание средств клиента по поддельным данным (оплата на произвольный договор без валидации данных) плюс массовая генерация чеков об оплате. Оценка критичности — critical.

Баг первый: обход валидации данных​

Эндпоинт API Для просмотра ссылки Войди или Зарегистрируйся позволяет обойти серверную валидацию входных параметров (номер договора, паспортные данные), сформировать действующую платежную ссылку и провести оплату деньгами с карты пользователя, даже если указанные договорные данные фиктивны. После завершения транзакции формируется официальная квитанция, подтверждающая оплату, несмотря на отсутствие проверки данных; деньги списываются с карты клиента в «фонд организации» на несуществующий договор — «в никуда».


Как воспроизвести​

Я начал исследование и знакомство с тестируемой организацией на ресурсе lk.domain.com и нажал первую попавшуюся кнопку «Сделать взнос». Далее заполнил форму взноса произвольными данными и нажал «Продолжить».

Пример заполнения
Пример заполнения

Ожидаемо получил ошибку со статусом 400 (bad request) и увидел, что номер договора и последние шесть цифр паспорта введены неверно.

Пример ошибки
Пример ошибки

Конечно, сразу хочется проверить реализацию проверки неверных параметров, но я не стал торопиться и открывать Burp. Зачастую достаточно осмотреться, «потыкать кнопки» и ближе познакомиться с логикой работы приложения, а также с его легитимными функциями и бизнес‑логикой. Этим я и занялся, открыв инструменты разработчика вместо Burp, — многие баги действительно можно найти без специализированных инструментов.

В коде кнопки «Продолжить» скрывался POST-запрос со всеми параметрами из GUI, токеном капчи и любопытным параметром step=0.

Вооружившись инструментами разработчика (DevTools), я открыл запрос со статусом 400 и воспользовался функцией Edit and Resend. В теле POST-запроса я изменил значение параметра step с 0 на 1 и отправил запрос повторно. Капча больше не мешала — она уже была успешно пройдена на предыдущем шаге через интерфейс сайта.

Я готовился к очередной ошибке валидации, но на деле далее я убедился в статусе 200 и получил ссылку на оплату на этом всё, баг найден, спасибо за прочтение.

Пример изменения атрибута step с 0 на 1 и повторная отправка запроса с получением ссылки на оплату
Пример изменения атрибута step с 0 на 1 и повторная отправка запроса с получением ссылки на оплату

После перехода по ссылке на payecom.ru наблюдаем платежку с указанной суммой и надписью «Фонд организации» (на скриншоте, к сожалению, необходимо замазать). Так как система оплаты (payecom.ru) выходит за скоуп, никакие манипуляции с ней я делать не пытался.

Однако убедиться в сформированной работающей платежной форме было необходимо (и крайне интересно в исследовательских целях).

Я использовал свою тестовую карточку и попробовал сделать минимальную транзакцию в «Фонд организации».

Ввод данных карты
Ввод данных карты
Успешная оплата
Успешная оплата
После успешной транзакции пользователь перенаправляется на такой адрес:

https://lk.domain.com/payment/complete?uf_hash=...&uf_pay_id=...

Там он видит кнопку «Распечатать квитанцию», а также подтверждение успешной оплаты.

Успешная оплата и сформированный PDF с фиктивным номером договора
Успешная оплата и сформированный PDF с фиктивным номером договора

Так как данные договора фиктивные, то в поле ФИО подставилась точка — это не мои закраски белым цветом.

info​

В качестве дополнительного подтверждения списания я приложил коллегам из триажа скриншоты из моего ЛК банка о проведенной транзакции в «Фонд организации».
Таким образом, можно отправлять деньги никуда на фиктивные (даже несуществующие) данные.

Другие поля​

Поля с паспортом и номером договора также могут быть заполнены произвольными данными (в частности, неполными цифрами паспорта и неполным номером договора).
Успешная попытка оплаты с неполными номерами договора и паспорта
Успешная попытка оплаты с неполными номерами договора и паспорта

Валидация поля с суммой amount реализована корректно (в рамках моих попыток манипуляций я не заметил никаких странностей). То есть если изменить amount на новое значение (например, с 500 рублей увеличить взнос до 600 или, наоборот, понизить), то ссылка сгенерируется с уже ранее введенной оригинальной суммой в 500 рублей. Скорее всего, это свидетельствует о том, что генерация платежной формы происходит где‑то на моменте первичного ввода неправильных данных или учитывается при GET-запросе к .../api/v3/autopays/default-values с дефолтным значением минимального платежа в 500 рублей.
Вот таким интересным способом и без Burp удалось обнаружить первую уязвимость критического уровня.

Она позволяет обойти необходимую валидацию входных данных и провести финансовую операцию с реальной оплатой, при этом сформировать официальную квитанцию на поддельные параметры. Это сочетание нарушений бизнес‑логики, уязвимостей верификации и отсутствия контроля на стороне сервера приводит к реальному риску финансового мошенничества и потери доверия к платформе.


Риски​

  • Нарушение бизнес‑логики: возможность проведения финансовой операции без прохождения проверки данных. Система не подтверждает, что договор существует, но создает реальный платеж.
  • Финансовые потери и правовые риски: деньги списываются и попадают в «Фонд организации», но могут быть не связаны с клиентом, что создает ситуацию «висячих» средств. Пользователь может предъявить квитанцию, требуя зачисления, даже если договор поддельный.
  • Мошенничество и фишинг: злоумышленник может генерировать квитанции на произвольные суммы и договоры, рассылать фишинговые ссылки с валидной формой оплаты, вводить в заблуждение сотрудников и клиентов.
То есть можно не только создать свой счет в «Фонде организации» и массово генерировать легитимные ссылки на оплату на свой счет, но и создавать произвольные ссылки с фиктивными данными, чтобы пользователь отправлял деньги в никуда. Я пока даже не могу представить, куда зачислились в итоге мои 500 рублей и где они лежат, если договор и паспорт несуществующие, а транзакция прошла успешно!

Это создает дополнительно и репутационные риски для организации: если пользователь обнаружит после фишинга или в процессе самостоятельных манипуляций с запросами (менее вероятно, но смотрим на картину произошедшего в целом), что при неверных (несуществующих) данных договора и паспорта его транзакция все‑таки возможна и деньги утекают куда‑то в фонд, это может понизить доверие клиентов к бренду.


Причина уязвимости​

  • Отсутствие строгой проверки параметров contractNumber, passport на step=1.
  • step передается от клиента и не контролируется сервером (нет session binding, флагов прохождения).
  • Возможность генерации валидной платежной формы без авторизации и без предварительной проверки данных.

Рекомендации​

  • Привязать step=1 к успешному step=0 через backend-сессию, временный токен или nonce.
  • Запретить генерацию платежного URL без успешной серверной валидации договора.
  • Дополнительно: включить audit и alerting (в общем — мониторинг) при множественных попытках создания платежей с одного IP или для одного договора (тут уже говорю о снижении поверхности атаки, чтобы массово нельзя было генерировать ссылки. Текущий механизм защиты в виде капчи легко обходится, так как ее можно запросить по прямому GET-запросу и самостоятельно вставить токен и валидное решение на основе сгенерированной картинки).
  • Ввести технические ограничения на доступ к API — валидация структуры договора, паспорта и их соответствия. Возможно, отказаться от генерации взносов в «Фонд организации» вне границ личного кабинета (сейчас я это сделал без какой‑либо авторизации по «Госуслугам» или входу в ЛК, так как кнопка «взноса» доступна без авторизации) и отключить эту опцию генерации взносов без предварительной верификации, чтобы не формировать массово ложные ссылки на оплаты и снизить риск социальной инженерии.
Но на этом история не заканчивается, друзья! Оказывается, данный баг открыл двери к новому недостатку.

Я решил продолжить исследовать и посмотреть на сгенерированные квитанции об оплате более детально.


Баг второй: массовая генерация чеков об оплате​

После оплаты взноса в «Фонд организации» можно массово генерировать квитанции с произвольными именами и исчерпать свободное место на веб‑сервере lk.domain.com. Сервер не проверяет на конечной точке

https://lk.domain.com/api/v3/pay?uf_hash=996d63e700aa83e2cc82ee4c51c6798b&uf_pay_id=<random_pay_id> соответствие между параметрами uf_hash и uf_pay_id.

При подаче валидного uf_hash (от реально существующей оплаты) можно вписывать произвольный текст в uf_pay_id, и система сгенерирует новый PDF с соответствующим именем, подставив значение из uf_pay_id.


Как воспроизвести​

Сделать оплату в «Фонд организации» на конечной точке Для просмотра ссылки Войди или Зарегистрируйся. Можно ввести легитимные данные, а можно по аналогии с моим репортом выше сформировать оплату по поддельным данным.

После проведения оплаты пользователя перенаправит на конечную точку Для просмотра ссылки Войди или Зарегистрируйся<hash_отidоплаты>&uf_pay_id=<id_оплатыссайта_payecom.ru>.

Перенаправление на конечную точку
Перенаправление на конечную точку

Получив валидное значение uf_hash, обратиться к эндпоинтам:

https://lk.domain.com/payment/complete?uf_hash=996d63e700aa83e2cc82ee4c51c6798b&uf_pay_id=965a3a6f-8ec8-55dc-16fb-674ff59c5647
Либо вызвать API напрямую также GET-запросом:

https://lk.domain.com/api/v3/pay?uf_hash=996d63e700aa83e2cc82ee4c51c6798b&uf_pay_id=965a3a6f-8ec8-55dc-16fb-674ff59c5647
После обращения к указанным эндпоинтам сформируется либо страница со ссылкой на PDF-квитанцию, либо JSON (если API вызвали напрямую) с той же ссылкой на PDF-квитанцию.

Легитимные ссылки на сформированные PDF с квитанциями
Легитимные ссылки на сформированные PDF с квитанциями

По описанным выше эндпоинтам можно подменять значение uf_pay_id= произвольным. Будет генерироваться новый PDF с такой же квитанцией, более того, значение uf_pay_id подставляется в имя нового PDF-файла. Так можно массово генерировать неограниченное число PDF на сервере.

PDF-документ доступен по прямой ссылке:

https://lk.domain.com/upload/invoices/Kvitanciya_po_operacii_fake_PoC_pdf_996d63e700aa83e2cc82ee4c51c6798b.pdf

Здесь fake_PoC_pdf — вхождение переменной uf_pay_id.

Для просмотра ссылки Войди или Зарегистрируйся
Далее я проверил, точно ли это новые документы, а не перезапись старого, сравнил хеш‑суммы и обратился к нескольким сгенерированным вручную документам. Все подтвердилось: я действительно могу после одной валидной транзакции (точнее, зная валидный uf_hash от транзакции) генерировать сотни, а может быть, даже тысячи документов. Оставалось продемонстрировать импакт и объяснить коллегам риски.

Каждый мой документ об оплате занимал ~19 Кбайт. Соответственно, сгенерировав 1 000 000 PDF-документов, можно заполнить ~19 Гбайт памяти на веб‑сервере.

Все сгенерированные документы хранятся по такому пути:

https://lk.domain.com/upload/invoices/Kvitanciya_po_operacii_...
В качестве PoC для массовой генерации PDF я решил приложить коллегам скрипт на Bash, который почему‑то я все‑таки написал быстрее, чем открыл бы Burp и проделал все там (скрипт генерирует уникальные PDF в количестве пяти штук для демонстрации DoS без негативных последствий):

Код:
HASH="996d63e700aa83e2cc82ee4c51c6798b" for i in $(seq 1 5); do PAY_ID="AbuseOkiDoki_$i" echo "[*] Запрос $i → PAY_ID=$PAY_ID" curl -s "https://lk.domain.com/api/v3/pay?uf_hash=$HASH&uf_pay_id=$PAY_ID" \ --compressed \ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" \ -H "Accept: application/json, text/plain, */*" \ -H "Referer: https://lk.domain.com/payment/complete?uf_hash=$HASH&uf_pay_id=test" \ -H "Origin: https://lk.domain.com" \ -H "Sec-Fetch-Site: same-origin" \ -H "Sec-Fetch-Mode: cors" \ -H "Sec-Fetch-Dest: empty" \ -H "Accept-Language: ru-RU,ru;q=0.9" \ -H "Connection: keep-alive" \ -H "X-Bug-Bounty:OkiDoki" echo -e "\n" sleep 1 done

Запуск написанного на коленке скрипта и генерация пяти чеков в PDF
Запуск написанного на коленке скрипта и генерация пяти чеков в PDF

Таким образом, сервер не проверяет, что uf_pay_id действительно связан с переданным uf_hash (то есть с транзакцией).

Если атакующий располагает одним валидным uf_hash (например, полученным от собственной тестовой транзакции, в рамках триажа я предложил коллегам взять мой uf_hash 996d63e700aa83e2cc82ee4c51c6798b), он может массово генерировать неограниченное число квитанций PDF с уникальными именами (так как параметр uf_pay_id= подставляется в имя документа) и израсходовать свободное место на веб‑сервере lk.domain.com.


Риски и причины​

DoS по дисковому пространству: генерация сотен, а может быть, даже тысяч PDF будет значить исчерпание свободного места на веб‑сервере. Злоумышленнику не нужно быть аутентифицированным на ресурсе, чтобы исчерпать свободное место на диске.

Причина — недостаточная привязка данных: uf_hash никак не защищен от повторного использования (злоупотребление именем конечного PDF), также возможен фаззинг и массовое формирование ошибок из‑за неверной попытки записи в файл при передаче некорректных данных в имя файла. Массовый вызов ошибок записи в файл с кодом 500 может также нагрузить систему или обработчик ошибок.


Рекомендации​

  • Валидировать соответствие uf_hash и uf_pay_id на сервере, а также исключить возможность записи PDF на веб‑сервер после изменения uf_pay_id (одной транзакции достаточно иметь одну PDF об оплате без возможности перезаписи или создания копии).
  • Исключить произвольный uf_pay_id из имени файла или заменять его UUID. Рассмотреть обращение к этим эндпоинтам за аутентификацией (возможно, вовсе отказаться от механизма взносов в фонд без авторизации по «Госуслугам» и прочим провайдерам аутентификации) или ввести одноразовые токены на генерацию PDF.
  • Скорректировать работу эндпоинта Для просмотра ссылки Войди или Зарегистрируйся, чтобы при изменении uf_pay_id не генерировался новый PDF, а использовался ранее созданный.
  • Очищать неиспользуемые файлы по TTL или по истечении времени хранения.
  • Смягчающая мера: ограничить число обращений к одному uf_hash (rate limit + TTL).

Выводы​

Таким образом, мне удалось отыскать две уязвимости, которые коллеги высоко оценили с точки зрения критичности, несмотря на то что я зашел в программу багбаунти далеко не первым и там ранее было сдано несколько приличных багов.

На мой взгляд, эта история подтверждает, что иногда достаточно посмотреть на бизнес‑логику под другим углом — и удача будет в кармане. Мне удалось продемонстрировать эксплуатацию, не открывая Burp Suite и вооружившись только инструментами разработчика.
 
Activity
So far there's no one here
Сверху Снизу