- PVSM.RU - https://www.pvsm.ru -

Электронные системы расчетов существуют в интернете уже давно, а баги на них встречаются двадцатилетней давности. Мы находили критические уязвимости, позволяющие угнать деньги и накрутить баланс. Сегодня мы разберем типовые реализации приема платежей и связанные с ними проблемы безопасности.
Мало кто знает, но первой (анонимной!) платежной системой был DigiCash [2], который появился аж в 1989 году, за ним, в 1996 году, последовала уже более известная (преимущественно среди кардеров) система E-gold [3].
Но вернемся в настоящее и перечислим основные современные крупные платежные системы/сервисы электронных платежей, которые позволяют принимать платежи на собственном веб-сайте:

А также десятки менее известных систем, названия которых вам ничего не скажут, не говоря уже о появлении сотен новых, специализирующихся на криптовалютах.
Несмотря на кажущуюся простоту, процесс приема платежей, с точки зрения создания безопасной программной реализации, представляет собой комплексный процесс, который до сих пор приводит к проблемам как у крупных торговых площадок, так и у новых электронных систем расчетов, которые периодически выходят на рынок с «новым и удобным» API и прочими способами интеграции. Как же выглядит типичный процесс приема платежа? Для начала давайте рассмотрим текущую реализацию, которую описывает PayPal, так называемый PayPal Express Checkout.

Данную реализацию можно считать относительно безопасной, и вот почему:
А теперь посмотрим на схему, которую нам предлагает WebMoney:

Схема ни хрена не понятная. Также схема не отражает ряд нюансов, вроде подписи запроса. Или информацию о том, что URL, который принимает на себя технические параметры платежа от платежной системы, и URL, куда пользователь будет перенаправлен для просмотра деталей об оплате, стоит делать разными. Архитектура, которую использует WebMoney, часто всплывает в той или иной форме и в других платежных системах, которые были созданы в СНГ.
Излишнее усложнение схемы приема платежей ведет к финансовым потерям. Например, 10 лет назад я публиковал заметку [4] о проблеме интеграции с WebMoney системы Global Collect Services, что приводило к возможности подтверждать платежи без оплаты в Steam, Battle.net и некоторых других.
В чем же состояла проблема? Ранее я упоминал URL на стороне продавца, которые должны принимать информацию о платеже. Согласно документации [5], у WebMoney существуют три сущности:
Что делают некоторые разработчики, которые читают эту документацию:

Как это работало:

Система приема платежей Global Collect успешно споткнулась о несколько проблем, которые упоминались выше:
Все это привело к возможности совершать фиктивные транзакции и покупать все, что использовало процессинг Global Collect, без ограничений. Проблему устранили только через ~2 недели массовой эксплуатации.
Другой вариант похожей проблемы, но чуть сложнее, был не так давно в Smart2Pay [9].
Еще одна проблема, связанная с подписью запросов - Length Extension Attack [10].

Или атака удлинением сообщения. Согласно Wikipedia — это тип атаки на хеш-функцию, заключающейся в добавлении новой информации в конец исходного сообщения. При этом новое значение хэша может быть вычислено, даже если содержимое исходного сообщения остаётся неизвестным. Чуть подробнее можно изучить здесь [11]. Проблема встречалась всего пару раз, когда разработчики решили реализовать свою “классную” подпись запросов в стиле VK (которые в общем-то тоже не сами придумали алгоритм), но получилось как обычно.
Ниже небольшая иллюстрация на тему, как допустимо генерировать подпись в таком вот стиле и как “выстрелить себе в ногу”.

Для эксплуатации же можно воспользоваться одним из следующих инструментов:
На сайте, где было доступно пополнение с помощью WooPay (через SMS), отображался полный URL с параметрами (включая подпись), по которому платежная система уведомляет сайт, если платеж успешно зачислен.Логика достаточно проста, нужно вызвать исключительную ситуацию, чтобы тестируемое веб-приложение вывело ошибку.
Если выбрать оплату через SMS и ввести случайный недействительный номер, то получаем:

Повторяем запрос сотню-другую раз. Отправив нас в бан, сайт начинал выводить exception, в тексте которого содержался тот самый секретный URL, при переходе по которому на счет зачисляются деньги.

Перейдем к проблеме проверки атрибутов платежа.
Один из вариантов интеграции с ЮMoney (ex Яндекс.Деньги), это форма для перевода [14] или ее старая реализация [15]. Опознать ее можно по наличию запросов к следующим адресам:
https://yoomoney.ru/eshop.xml
https://yoomoney.ru/quickpay/confirm.xml
Прямо при отправке запроса необходимо подменить число, которое запрашивает тестируемое веб-приложение:

Высокая вероятность, что платеж пройдет. А дальше сайт либо проверяет сумму, либо нет. Так как в комментарии к платежу передается идентификатор пользователя и/или идентификатор платежа, этого достаточно, чтобы оформить подписку на какой-то сервис.

Небольшой пример:

На скриншоте происходит оплата подписки на бота голосового ассистента в Telegram, но сумма платежа не проверяется, что дает возможность приобрести продукт за произвольную цену. Подменяем сумму 1990 на 19, получаем нужную подписку. Этот тип проблем часто встречается (например, много сервисов-ботов в Telegram, о которых многие слышали, и где эта проблема до сих пор присутствует), в том числе на зарубежных ресурсах (пример из старых — покупка лицензии Minecraft [16]), в 2022 году, хотя казалось бы…
Еще один релевантный пример, но связанный не с суммой платежа, а с валютой, присутствовал у QIWI. В кошельке была опция пополнения путем отправки SMS на короткий номер, причем валюта передавалась через браузер клиента на нескольких этапах (выбор валюты и суммы, отправка SMS), где сервер доверял данным клиента. В итоге на счет зачислялось 100$, а оплата на 100р.
А что там в 2022?
Смотрим в чат армянского банка https://t.me/Inecobank_forum/6333 [17]


Значит, проблема до сих пор актуальна.

Занимательная проблема присутствовала на широко известном в узких кругах сервисе Антикапча [18] (сервис по разгадыванию капчи за деньги с API интерфейсом). Личный кабинет пользователя позволял совершать ряд операций, в т.ч. выводить неиспользованный баланс на WebMoney. WebMoney нормально воспринимает сумму платежа в различных нотациях (например, со значением 1e1 или 0xFF), а сравнение подобных чисел, еще и на старых версиях PHP, еще и с учетом нюансов сравнения в языке PHP, еще и с порцией “качественного кода” приводило к самым неожиданным последствиям.
В шестнадцатеричной нотации сравнение текущего баланса с запрашиваемой на вывод суммой работало некорректно, что позволяло уводить баланс аккаунта в минус. Пример возможной логики перевода денег:

Если на вход подать 1e9, имея на балансе 20 долларов, механизм проверки удостоверится, что 20>19, вырезав все кроме цифр, а процессинг обработает 1e9 как 1000000000.
Другая ошибка — это особенности приведения типов.

NodeJS — пример языка с динамической типизацией. Когда прибавляешь к числу строку, то произойдет конкатенация 1+"1" = "11". Но стоит из строки вычесть число, то уже строка приводится к числу "11"-1 = 10. Самый популярный формат обмена данными — JSON:
{"amount":100}
Справедливо, что JSON будет корректным: параметр amount с числом 100. Но и этот вариант будет корректным JSON’ом:
{"amount": "100"}
Только содержимое параметра amount будет строкой, но из-за особенностей обработки подобного запроса, возможно, кто-то приплюсует к числу 1337 значение этого параметра, и получится 1337100, а не то, что задумывалось изначально.

В ДБО создается платеж, чтобы его подтвердить, необходимо ввести код из SMS. Платеж сохраняется как неисполненный и доступен для редактирования в мобильном приложении. Редактируем платеж, после этого в браузере вводим код подтверждения, и итоговый перевод совершается с одной суммой, а со счета списывается другая.Другой пример:
Отдельного упоминания заслуживает работа корзины на ресурсах, где используется несколько валют. Уязвимость, которая была в магазине Xbox несколько лет назад (причем после исправления появлялась еще пару раз):

Голоса в VKontakte [19] генерировались с помощью SMS со счета с около-нулевым балансом. Отправляешь SMS, оператор связи не может забрать деньги (овердрафт [20] отсутствует), а голоса пополняются.
Другой вектор через SMS — это перевод со своего счета на чужой в платежной системе QIWI. Это делалось через отправку сообщения на специальный короткий номер:

Но дело в том, что короткий номер — это алиас настоящего телефонного номера, который участвует в SMS-шлюзе для интеграции с API. Применяем немного социальной инженерии:

Дальнейшие шаги — использовать сервисы по подмене номера, чтобы отправить туда SMS от аккаунта, на котором много денег. Способ так и не проверен, хотя в теории выглядит крайне забавно. Очевидцы говорят, что подобным образом можно было привязать карту через SMS, а дальше сливать деньги с карты.
А теперь изучим операцию возврата средств (refund). Если рассмотреть каноничный процесс возврата, то станет очевидно, что на каждом этапе можно пропустить или некорректно реализовать все проверки, что приведет к финансовым потерям.

На практике встречались площадки, где возврат средств по транзакции происходил таким образом, что сумма возврата бралась от актуальной текущей стоимости товара, а не из информации о проведенной транзакции. Вместе с периодическими скидками, это приводило к понятным результатам. Ситуация редкая, но иногда встречается в той или иной форме.
Частая категория проблем — ошибки округления чисел. Распространенные проблемы с округлением могут выглядят следующим образом:

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

Если присмотреться, то можно увидеть уязвимость, хоть она и не актуальна на текущий момент.
С переполнениями и операциями с числами с отрицательным знаком также периодически можно столкнуться, даже в банках из топ 100. Перевод отрицательной суммы – тривиальный пример при работе с числами со знаком, и да, такое тоже до сих пор встречается.
Менее тривиальный пример про переполнения – подсчет суммы заказа при добавлении большого числа товаров в корзину.

Еще один пример – это восприятие больших сумм при передаче между системами. В HTTP-запросе передаваемое число будет строкой, но вот обработка большого числа может отличаться, т.е. отправляется запрос на пополнение на больше чем INT_MAX [21]+2, на локальной системе число обрабатывается корректно, а в платежной системе получаем счет на оплату размером в 1$.
Стоит учитывать, что тестируемая система может использовать не 32-разрядную переменную для хранения значения, а 64-разрядную.Чтобы лучше понять, можно потыкать циферки в вконтакте. Раньше в VKontakte все числа были 32-разрядные. Чтобы получить id1 с помощью переполнения, необходимо было посчитать 2^32+1.
Страница Дурова открывалась под https://vk.com/id4294967297 [22]. Но сейчас уже все переведено на 64-разрядные числа, поэтому, чтобы получить единицу, необходимо посчитать 2^64+1 [23].
Это одни и те же страницы:
https://vk.com/id1 [24] == https://vk.com/id18446744073709551617 [25]
А теперь представь, что в веб-приложении операцию с id=100 может выполнить только администратор? А если это операция с 2^32+100?
Кстати, иногда можно не рассчитать с циферками, и уйти глубоко в минус, так и не достигнув плюса.

Перейдем к такой проблеме, как состояние гонки (англ. race condition). Согласно Wikipedia – это ошибка проектирования многопоточной системы или приложения, при которой работа системы или приложения зависит от того, в каком порядке выполняются части кода. Своё название ошибка получила от похожей ошибки проектирования электронных схем.
Условно каноничный пример:

Небольшой пример, связанный с криптовалютной биржей.

Алгоритм эксплуатации был следующий:
Эта категория проблем не специфична для финансовых операций. Сюда же относятся проблемы типа TOCTOU [26], когда, например, приложение проверяет подпись на файле, далее некоторое окно, и далее работа с содержимым файла (а содержимое возможно подменить в рамках окна).
Одна из проблем присутствовала на xss.is [27] в системе перевода BTC между учетными записями.
Для тестирования можно использовать Burp Suite с плагином Turbo Intruder [28]. А подробнее об этой категории проблем можно почитать в статье [29].
Итак, кладем на депозит 0.1337 BTC, отправляем множество запросов на перевод.

Видим, что отправка перевода выполнилась больше раз, чем было денег на балансе:

Отправляем крипту обратно. Продолжаем гонять деньги туда-сюда под разными аккаунтами, генерируя деньги из воздуха:

Получаем деньги на счету. Только на самом деле такого депозита не было, поэтому выводить можно до тех пор, пока подключенный кошелек (со всеми депозитами пользователей) не опустеет.

Я думаю, существуют и другие форумы, в которых возможны депозиты и автоматический вывод без ручного подтверждения.
Реализация безопасного приема платежей — это комплексная задача, которой должны заниматься опытные разработчики. Получившийся продукт необходимо всесторонне тестировать, иначе мы еще не один десяток лет будем наблюдать детские проблемы безопасности из начала нулевых, особенно при появлении новых классных способов платежей (привет, криптовалюты) и сопутствующих платежных систем. И это мы еще не упоминали атаки на генераторы псевдослучайных чисел, Padding Oracle, и множество других веселых штук, которые заслуживают отдельной статьи.
Автор: Сергей Белов
Источник [32]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/informatsionnaya-bezopasnost/378174
Ссылки в тексте:
[1] yarbabin: https://habr.com/ru/users/yarbabin/
[2] DigiCash: https://ru.wikipedia.org/wiki/DigiCash
[3] E-gold: https://ru.wikipedia.org/wiki/E-gold
[4] заметку: https://kaimi.io/2011/03/steam-games-for-free/
[5] документации: https://wiki.webmoney.ru/projects/webmoney/wiki/web_merchant_interface
[6] нотацию: https://www.php.net/manual/ru/language.types.integer.php
[7] нюансов сравнения в языке PHP: https://www.php.net/manual/en/types.comparisons.php
[8] race condition: https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8
[9] Smart2Pay: https://hackerone.com/reports/1295844
[10] Length Extension Attack: https://en.wikipedia.org/wiki/Length_extension_attack
[11] здесь: https://habr.com/ru/post/181372/
[12] https://github.com/bwall/HashPump: https://github.com/bwall/HashPump
[13] https://github.com/iagox86/hash_extender: https://github.com/iagox86/hash_extender
[14] форма для перевода: https://yoomoney.ru/docs/payment-buttons/using-api/forms
[15] старая реализация: https://web.archive.org/web/20210225030211/https://yookassa.ru/docs/payment-solution/payment-form/basics
[16] покупка лицензии Minecraft: https://kaimi.io/2011/09/almost-for-free-minecraft/
[17] https://t.me/Inecobank_forum/6333: https://t.me/Inecobank_forum/6333
[18] Антикапча: https://anti-captcha.com/
[19] VKontakte: http://vk.com/
[20] овердрафт: https://ru.wikipedia.org/wiki/%D0%9E%D0%B2%D0%B5%D1%80%D0%B4%D1%80%D0%B0%D1%84%D1%82
[21] INT_MAX: https://www.geeksforgeeks.org/int_max-int_min-cc-applications/#:~:text=Value%20of%20INT_MAX%20is%20%2B2147483647.
[22] https://vk.com/id4294967297: https://vk.com/id4294967297
[23] 2^64+1: https://www.wolframalpha.com/input?i=%282%5E64%2B1%29
[24] https://vk.com/id1: https://vk.com/id1
[25] https://vk.com/id18446744073709551617: https://vk.com/id18446744073709551617
[26] TOCTOU: https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use
[27] xss.is: http://xss.is/
[28] Turbo Intruder: https://portswigger.net/bappstore/9abaa233088242e8be252cd4ff534988
[29] статье: https://habr.com/ru/post/460339/
[30] Kaimi: https://kaimi.io/
[31] Bo0oM: https://bo0om.ru/20-years-of-payment-processing-problems
[32] Источник: https://habr.com/ru/post/683846/?utm_source=habrahabr&utm_medium=rss&utm_campaign=683846
Нажмите здесь для печати.