• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья 20 лет проблем приема платежей 💸

Bo0oM

1'"--></script><img/src/onerror=alert()>
Эксперт
Регистрация
21.01.2019
Сообщения
71
Реакции
367
mhjdeojixxzlncws1f0cqoazbly.png

TL;DR​


Электронные системы расчетов существуют в интернете уже давно, а баги на них встречаются двадцатилетней давности.

Мы находили критические уязвимости позволяющие угнать деньги и накрутить баланс.

Сегодня мы разберем типовые реализации приема платежей и связанные с ними проблемы безопасности.


Обзор платежных систем и типовых реализаций API​


Мало кто знает, но первой (анонимной!) платежной системой был DigiCash, который появился аж в 1989 году, за ним, в 1996 году, последовала уже более известная (преимущественно среди кардеров) система E-gold.

Но вернемся в настоящее и перечислим основные современные крупные платежные системы/сервисы электронных платежей, которые позволяют принимать платежи на собственном веб-сайте:


  • PayPal
  • WebMoney
  • ЮMoney (бывшие Яндекс.Деньги)
  • Qiwi
  • Alipay
  • и т.д.
wzfumdhilrrv3ghzwyz3psidsou.png


А также десятки менее известных систем, названия которых вам ничего не скажут, не говоря уже о появлении сотен новых, специализирующихся на криптовалютах.

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



goirfro2o78bxxoxbsmj3hx6thk.png

Данную реализацию можно считать относительно безопасной и вот почему:


  • Параметры платежа не передаются явным образом, вместо этого используется Token
  • Сервер платежной системы не отправляет результаты на некий URL самостоятельно, вместо этого ваш веб-сайт должен самостоятельно их запросить и обработать ответ
  • В целом схема взаимодействия реализована так, что у потенциального разработчика существует минимум возможностей "выстрелить себе в ногу"

А теперь посмотрим на схему, которую нам предлагает WebMoney:


ayvfa_6ftboh2_3u8ckt_th5xik.png

Схема ни хрена не понятная. Также схема не отражает ряд нюансов, вроде подписи запроса. Или информацию о том, что URL, который принимает на себя технические параметры платежа от платежной системы и URL, куда пользователь будет перенаправлен для просмотра деталей об оплате, стоит делать разными. Архитектура, которую использует WebMoney, часто всплывает в той или иной форме и в других платежных системах, которые были созданы в СНГ.

Типовые проблемы​

Излишнее усложнение схемы приема платежей ведет к финансовым потерям. Например, 10 лет назад Kaimi публиковал заметку о проблеме интеграции с WebMoney системы Global Collect Services, что приводило к возможности подтверждать платежи без оплаты в Steam, Battle.net и некоторых других.


В чем же состояла проблема? Ранее я упоминал URL на стороне продавца, которые должны принимать информацию о платеже. Согласно документации, у WebMoney существуют три сущности:

  • Success URL - URL (на веб-сайте продавца), на который будет переведен интернет-браузер покупателя в случае успешного выполнения платежа в сервисе Web Merchant Interface. URL имеет префикс "http://" или "https://".
  • Fail URL - URL (на веб-сайте продавца), на который будет переведен интернет-браузер покупателя в том случае, если платеж в сервисе Web Merchant Interface не был выполнен по каким-то причинам. URL имеет префикс "http://" или "https://".
  • Result URL - URL (на веб-сайте продавца), на который сервис Web Merchant Interface посылает HTTP POST или SMTP-оповещение о совершении платежа с его детальными реквизитами. URL должен начинаться с префикса "http://", "https://" или "mailto:".

Что делают некоторые разработчики, которые читают эту документацию:
  1. Обрабатывают информацию по одному URL, что приводит к возможности узнать адрес обработчика (также обработчик, в том числе Result URL, может отображаться в платежной форме на сайте WebMoney, но такое происходит не всегда и, вероятно, зависит от настроек).
  2. Некорректно реализуют проверку подписи для запроса, который приходит на Result URL. Это позволяет клиенту подменить данные о платеже.
  3. Проверяют подпись, но не проверяют сумму, которая пришла на Result URL. Это позволяет получить товар за 100$, заплатив, например, 0.01$.
  4. Проверяют подпись, сумму, но не нотацию передаваемых сумм. Помните я упоминал о передаче параметров платежа через браузер клиента? Так вот, WebMoney абсолютно нормально воспринимает сумму платежа со значением 1e1 или 0xFF, а сравнение подобных чисел, еще и на старых версиях PHP, еще и с учетом нюансов сравнения в языке PHP, приводило к самым неожиданным последствиям.
  5. Не совсем проблема платежной системы, НО, как насчет race condition и одинаковых внутренних идентификаторов платежей на ресурсе продавца? Привет, мультипликация баланса.
  6. ...

Подпись запросов​

ocxmyim6js1odi18_pmsgjh5kfs.png
Как это работало:
  1. При оплате через WebMoney, пользователя, в соответствии со спецификацией платежной системы, перенаправляло на сайт WebMoney, где он мог видеть сумму платежа, номер счета и прочие параметры.
  2. После нажатии кнопки “Далее” и аутентификации в системе становилась доступна информация об URL, который отвечал за обработку результата платежа (Result URL).
  3. Пользователь мог сформировать запрос к целевому URL, который, в соответствии со спецификацией WebMoney (ну почти), информировал платежную систему о том, что платеж успешно проведен.
  4. Profit!
ovldtjilil9qydvh6ndq5ovukm0.png

Система приема платежей Global Collect успешно споткнулась о несколько проблем, которые упоминались выше:

  • Известный единый обработчик результатов платежа
  • Отсутствие проверки подписи (да и отсутствие подписи в запросе как таковой)
  • Использование данных, передаваемых через браузер пользователя, в качестве (хотя, согласно спецификации WebMoney, это можно было делать через коллбэк, приходящий от серверов WebMoney)
Все это привело к возможности совершать фиктивные транзакции и покупать все, что использовало процессинг Global Collect, без ограничений. Проблему устранили только через ~2 недели массовой эксплуатации.

Другой вариант похожей проблемы, но чуть сложнее, был не так давно в Smart2Pay.


Еще одна проблема, связанная с подписью запросов - Length Extension Attack.


fngkwrt5plfgngqfylmbci9i3fe.png

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


Ниже небольшая иллюстрация на тему как допустимо генерировать подпись в таком вот стиле и как “выстрелить себе в ногу”.

af1gqv1rk4j2fxhbczdggi3pfjc.png

Для эксплуатации же можно воспользоваться одним из следующих инструментов:

https://github.com/bwall/HashPump

https://github.com/iagox86/hash_extender

Раскрытие “Result URL”​

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

Если выбрать оплату через SMS и ввести случайный недействительный номер, то получаем:


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

u35znqlnl787-r9ojixg1ay3d_o.png

Атрибуты платежа​

Перейдем к проблеме проверки атрибутов платежа.

Один из вариантов интеграции с ЮMoney (ex Яндекс.Деньги), это форма для перевода или ее старая реализация. Опознать ее можно по наличию запросов

https://yoomoney.ru/eshop.xml

или

https://yoomoney.ru/quickpay/confirm.xml

Прямо при отправке запроса необходимо подменить число, которое запрашивает тестируемое веб-приложение:

wtiptduqwjg1g60svn03vknvfhu.png

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

iram94qzfaiurubyn34nykkhuag.png

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

b7zsvwr4zii1uqq7j_59hjeskkm.png

На скриншоте происходит оплата подписки на бота голосового ассистента в Telegram, но сумма платежа не проверяется, что дает приобрести продукт за произвольную цену. Подменяем сумму 1990 на 19, получаем нужную подписку. Этот тип проблем часто встречается (например, много сервисов-ботов в Telegram, о которых многие слышали и где эта проблема до сих пор присутствует), в том числе на зарубежных ресурсах (пример из старых - покупка лицензии Minecraft), в 2022 году, хотя казалось бы…


Домашнее задание #1
Найти платный telegram-бот, попробовать оплатить подписку минимальной суммой


Еще один релевантный пример, но связанный не с суммой платежа, а с валютой, присутствовал у QIWI. В кошельке была опция пополнения путем отправки SMS на короткий номер, причем валюта передавалась через браузер клиента на нескольких этапах (выбор валюты и суммы, отправка SMS), где сервер доверял данным клиента. В итоге на счет зачислялось 100$, а оплата на 100р.


А что там в 2022?


Смотрим в чат армянского банка
https://t.me/Inecobank_forum/6333

9_r1mj3nygiylz4sk3cwc6f3w3o.png


4m_1wbapyclrhwlqn1hzs1oemuw.png

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


Нотации и сравнение типов​


0vtptotbi0fa9sdbhnwf_dkjov8.png

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

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

Пример возможной логики перевода денег:


q0szifgao_y4l0tfdu1ntqfl7ri.png

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


Другая ошибка - это особенности приведения типов.


7mmi9bsqvszophoosjhptgtu4aa.png

NodeJS - пример языка с динамической типизацией. Когда прибавляешь к числу строку, то произойдет конкатенация 1+"1" = "11" Но стоит из строки вычесть число, то уже строка приводится к числу "11"-1 = 10


Самый популярный формат обмена данными - JSON:


{"amount":100}


Справедливо, что JSON будет корректным: параметр amount с числом 100. Но и этот вариант будет корректным JSON’ом:


{"amount": "100"}


Только содержимое параметра amount будет строкой, но из-за особенностей обработки подобного запроса, возможно кто-то приплюсует к числу 1337 значение этого параметра, и получится 1337100, а не то, что задумывалось изначально)



Ошибки бизнес-логики​

2hqd0kcubva8wgv1gwerrbbd574.png
В ДБО создается платеж, чтобы его подтвердить, необходимо ввести код из SMS. Платеж сохраняется как неисполненный и доступен для редактирования в мобильном приложении. Редактируем платеж, после этого в браузере вводим код подтверждения и итоговый перевод совершается с одной суммой, а со счета списывается другая.


Другой пример:


  1. Вызываем механизм пополнения баланса (на балансе $1000, пополняем на $100).
  2. Веб-приложение запоминает текущий баланс.
  3. В это время тратим (отправляем на второй аккаунт) деньги.
  4. После выполнения транзакции баланс окажется $1100.

Отдельного упоминания заслуживает работа корзины на ресурсах, где используется несколько валют. Уязвимость которая была в магазине Xbox несколько лет назад (причем после исправления появлялась еще пару раз):


  1. Кладёшь в корзину товар за минимальную цену в рублях.
  2. Ищешь в магазине дорогие игры, цены на которые указаны в долларах.
  3. Добавляешь их в корзину.
  4. Магазин считает сумму позиций, но перерасчёт от доллара к рублю происходит в соотношении один к одному.

n4bp6ehg7dlhrdbvccwvw5u4dy8.png

Голоса в vk.com генерировались с помощью SMS со счета с около-нулевым балансом. Отправляешь SMS, оператор связи не может забрать деньги (овердрафт отсутствует), а голоса пополняются.


Другой вектор через SMS - это перевод со своего счета на чужой в платежной системе QIWI. Это делалось через отправку сообщения на специальный короткий номер:



x_y4ycvvpeslatvrizco5zts-qi.png


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

9uozio8ofaxhxs-vxfspn6fuf6q.png
Дальнейшие шаги — использовать сервисы по подмене номера, чтобы отправить туда SMS от аккаунта, на котором много денег. Способ так и не проверен, хотя в теории выглядит крайне забавно). Очевидцы говорят, что подобным образом можно было привязать карту через SMS, а дальше сливать деньги с карты.

А теперь изучим операцию возврата средств (refund). Если рассмотреть каноничный процесс возврата, то станет очевидно, что на каждом этапе можно пропустить или некорректно реализовать все проверки, что приведет к финансовым потерям.


bd8ddnizny3hfybr119xjz3rkba.png

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

Ошибки округления, переполнения и числа с отрицательным знаком​


Частая категория проблем - ошибки округления чисел. Распространенные проблемы с округлением могут выглядят следующим образом:
utg3d-szmocpqtuqlmmhmo6f_uy.png
  1. Пользователь переводит 0,29 RUB в доллары США.
  2. При стоимости одного доллара в 60 RUB, сумма в 0,29 RUB соответствует 0,00483333333333333333333333333333 USD.
  3. Данная сумма будет округлена до двух знаков после запятой, т. е. до 0,01 USD (один цент).
  4. Затем пользователь переводит 0,01 USD обратно в рубли и получает 0,60 RUB.
  5. Таким образом пользователь «выигрывает» 0,31 RUB.

Проблему до сих пор можно встретить в крупных финансовых организациях (различных банках и биржах).
jwkapcawheul1qdyjtv8x-4iccm.png
Если присмотреться, то можно увидеть уязвимость, хоть она и не актуальна на текущий момент. Пусть это тоже будет домашкой.


Домашнее задание #2

Понять, в чем уязвимость на скриншотах выше


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


Менее тривиальный пример про переполнения – подсчет суммы заказа при добавлении большого числа товаров в корзину.


igfaioks3rj9zlztdvughd_vn9m.png
Еще один пример – это восприятие больших сумм при передаче между системами. В HTTP-запросе передаваемое число будет строкой, но вот обработка большого числа может отличаться, т.е. отправляется запрос на пополнение на больше чем INT_MAX+2, на локальной системе число обрабатывается корректно, а в платежной системе получаем счет на оплату размером в 1$.


Стоит учитывать, что тестируемая система может использовать не 32-разрядную переменную для хранения значения, а 64-разрядную.


Чтобы лучше понять, можно потыкать циферки в вконтакте. Раньше в vk.com все числа были 32-разрядные. Чтобы получить id1 с помощью переполнения, необходимо было посчитать 2^32+1.


Страница Дурова открывалась под https://vk.com/id4294967297


Но сейчас уже все переведено на int64, поэтому, чтобы получить единицу, необходимо посчитать 2^64+1


Это одни и те же страницы: https://vk.com/id1 == https://vk.com/id18446744073709551617


А теперь представь, что в веб-приложении операцию с id:100 может выполнить только администратор? А если это операция с 2^32+100?



Домашнее задание #3

У Дурова есть и другие “алиасы”. Найди тот, что начинается на 3689


З.Ы. Иногда можно не рассчитать с циферками, и уйти глубоко в минус, так и не достигнув плюса)

itulss3xwkcau7ughb41t0qbhde.jpeg

Состояние гонки​


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


Условно каноничный пример:

xlxhvj5ybr8n1a-4jf-ajsmjzpa.png

  1. Выполняем операцию на перевод средств в рамках баланса.
  2. Совершаем ту же операцию N раз, где баланс должен закончиться при N-1 или больше, но отправляя запросы с минимальной задержкой (тут на помощь приходит HTTP-пайплайнинг, особенности с HTTP2 (все в рамках одной TCP-сессии) и т.п.).
  3. Наблюдаем минус на балансе.

Небольшой пример, связанный с криптовалютной биржей.
mwxgfjyag97k7cf0wbhjnvxvnny.png
Алгоритм эксплуатации был следующий:

  • Создаем тейк-профит на 0,1 BTC, когда стоимость биткойна будет равна $100,000
  • Биржа изымает (блокирует) в аккаунте 0,1 BTC на будующий тейк-профит
  • Удаляем тейк-профит отправляя 438695936458926734 запросов
  • Биржа «возвращает» нам 0,1 x N BTC, где N, количество одновременно выполненных операций

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


Воруем деньги со счетов xss.pro​


Одна из проблем присутствовала на xss.pro в системе перевода BTC между учетными записями.

Для тестирования можно использовать Burp Suite с плагином Turbo Intruder. А подробнее об этой категории проблем можно почитать в статье.

Для этого на депозит кладем 0.1337 BTC, отправляем множество запросов на перевод.


8-fknzlmkbgryi2qlpilw4utgvk.jpeg

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

cl7ck04urxedmpqp4n1dejinsbk.png

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

xswrwlkfhjiuvh25lipk7s_pwnk.png

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

dtrcaj0ludxo_rvco_62iate2yu.jpeg

Я думаю, есть и другие форумы, в которых возможны депозиты и автоматический вывод без ручного подтверждения.


Домашнее задание #4


Если это все время работало на xss.pro, быть может, до сих пор работает и на других форумах?

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

Резюме​


Реализация безопасного приема платежей - это комплексная задача, которой должны заниматься опытные разработчики. Получившийся продукт необходимо всесторонне тестировать, иначе мы еще не один десяток лет будем наблюдать детские проблемы безопасности из начала нулевых, особенно при появлении новых классных способов платежей (привет, криптовалюты) и сопутствующих платежных систем. И это мы еще не упоминали атаки на генераторы псевдослучайных чисел, Padding Oracle, и множество других веселых штук, которые заслуживают отдельной статьи.

--
Kaimi & Bo0oM
 
Последнее редактирование:
PDF, для более комфортного чтения
 

Вложения

  • 20_years.pdf
    2.5 МБ · Просмотры: 391


И, пожалуй, самое прикольное:
Скрытое содержимое

Статья - топ!
Хорошо, что премиум купил. Это просто имба!
P.S. Пацаны, не сливайте хайд!
 
Пожалуйста, обратите внимание, что пользователь заблокирован
У вас должно быть более 6500 сообщений для просмотра скрытого контента.
ыы забыл уже когда личный просил, но в этот раз - дайте личный:D
 
Статья класс.
Первая картинка со стимом и ВМ кажется еще у каими в блоге была в 2009-2010 году
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Я думаю, есть и другие форумы, в которых возможны депозиты и автоматический вывод без ручного подтверждения.
и сейчас множество людей поскачут на дарк форумы долбить их))) А статья очень интересная... плюс в карму
 
Все для моих солнышек
Скрытое содержимое
Только не сливайте!
Блин, попросил же не сливать. Что за люди пошли((
Пусть премиум берут, если хотят получить норм инфу. Развелось халявщиков!
Сейчас разлетится по всем форумам и все, кому не лень, начнут абузить эту тему(
 
Последнее редактирование:
Огонь статейка! Сначала думаю, с водой какой то. Не понял, а потом кааааак понял :)
Давайте мне последний личный точно больше никому :)
И вообще не верится что еще такое можно встретить в современных платежках
Теперь самое главное удержаться и не начать так каждый шоп тестить
Маша кст забавный робот, я тестил когда еще бесплатная была на бете
 
Пожалуйста, обратите внимание, что пользователь заблокирован
For the BTC part, we use Turbo intruder but where do we send the requests?
Thanks, great article.
 
Последнее редактирование:
Интересно очень. На дворе 2022 год вроде, все в крипту идут, децентрализация и все в этом духе, а тут 20 летней давности косяки))
Думаю все это из-за не внимательности. Мало таких внимательных как эти Kaimi и Bo0oM ))

Ps: Скиньте хайд последнему, очень интересно глянуть.
 
Ребята, хайд открывать всем не собираюсь, но если вы покажите вовлеченность и выполните домашнее задание, хотя бы 2 из 4 (на троечку), то покажу.

Пруфы о выполненном ДЗ прикрепляйте к теме под личный хайд.

Хотел заключительную часть статьи оставить только для элиты. Не думал, что платные пользователи видят ВЕСЬ хайд, но хорошо, все к лучшему.
 
Последнее редактирование:
Также хотел бы привести один типовой пример, с которым я столкнулся еще в конце 90-х, начале 2000-х, когда не знал, что такое race condition.

Untitled.png


  • Личный кабинет интернет оператора предлагал пополнять баланс с использованием карт оплаты с различным номиналом.
  • При этом можно было завести произвольное число личных кабинетов (не требовалось никакой активации по e-mail, телефону или еще как)
  • При этом пользовательская сессия в личном кабинете передавалась в URL (привет личные кабинеты 90-х, где Cookies редко использовались). Этот момент важен, т.к. в то время я умел программировать только на QBasic и ЛОГО.
  • Открывалось несколько окон пополнения баланса для разных учетных записей, далее соединение по модему обрывалось.
  • Вводились данные карточек в несколько форм пополнения и нажималась кнопка пополнить.
  • Далее происходил дозвон (в рамках 30 секундного таймаута браузера на совершение операции) и при подключении 2-3 запроса (из где-то 6) проходили одновременно.
  • Одна карта применялась на несколько учетных записей.
  • Profit!
 
А с помощью какой утилиты происходит подмена ответа на запрос страницы оплаты на примере Маши? :(
Нашел расширение для хрома - Resourse Override, но мне кажется оно не подходит
 
А с помощью какой утилиты происходит подмена ответа на запрос страницы оплаты на примере Маши? :(
Мы делаем это с помощью привычного для нас инструмента - burp suite.
Можно как подменить ответ, так и запрос) По нему много мануалов.
Машу скорее всего пофиксили, но есть всякие другие боты, как минимум один по пробиву уязвим)
 
Последнее редактирование:
mhjdeojixxzlncws1f0cqoazbly.png

TL;DR​


Электронные системы расчетов существуют в интернете уже давно, а баги на них встречаются двадцатилетней давности.

Мы находили критические уязвимости позволяющие угнать деньги и накрутить баланс.

Сегодня мы разберем типовые реализации приема платежей и связанные с ними проблемы безопасности.


Обзор платежных систем и типовых реализаций API​


Мало кто знает, но первой (анонимной!) платежной системой был DigiCash, который появился аж в 1989 году, за ним, в 1996 году, последовала уже более известная (преимущественно среди кардеров) система E-gold.

Но вернемся в настоящее и перечислим основные современные крупные платежные системы/сервисы электронных платежей, которые позволяют принимать платежи на собственном веб-сайте:


  • PayPal
  • WebMoney
  • ЮMoney (бывшие Яндекс.Деньги)
  • Qiwi
  • Alipay
  • и т.д.
wzfumdhilrrv3ghzwyz3psidsou.png


А также десятки менее известных систем, названия которых вам ничего не скажут, не говоря уже о появлении сотен новых, специализирующихся на криптовалютах.

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



goirfro2o78bxxoxbsmj3hx6thk.png

Данную реализацию можно считать относительно безопасной и вот почему:


  • Параметры платежа не передаются явным образом, вместо этого используется Token
  • Сервер платежной системы не отправляет результаты на некий URL самостоятельно, вместо этого ваш веб-сайт должен самостоятельно их запросить и обработать ответ
  • В целом схема взаимодействия реализована так, что у потенциального разработчика существует минимум возможностей "выстрелить себе в ногу"

А теперь посмотрим на схему, которую нам предлагает WebMoney:


ayvfa_6ftboh2_3u8ckt_th5xik.png

Схема ни хрена не понятная. Также схема не отражает ряд нюансов, вроде подписи запроса. Или информацию о том, что URL, который принимает на себя технические параметры платежа от платежной системы и URL, куда пользователь будет перенаправлен для просмотра деталей об оплате, стоит делать разными. Архитектура, которую использует WebMoney, часто всплывает в той или иной форме и в других платежных системах, которые были созданы в СНГ.

Типовые проблемы​

Излишнее усложнение схемы приема платежей ведет к финансовым потерям. Например, 10 лет назад Kaimi публиковал заметку о проблеме интеграции с WebMoney системы Global Collect Services, что приводило к возможности подтверждать платежи без оплаты в Steam, Battle.net и некоторых других.


В чем же состояла проблема? Ранее я упоминал URL на стороне продавца, которые должны принимать информацию о платеже. Согласно документации, у WebMoney существуют три сущности:

  • Success URL - URL (на веб-сайте продавца), на который будет переведен интернет-браузер покупателя в случае успешного выполнения платежа в сервисе Web Merchant Interface. URL имеет префикс "http://" или "https://".
  • Fail URL - URL (на веб-сайте продавца), на который будет переведен интернет-браузер покупателя в том случае, если платеж в сервисе Web Merchant Interface не был выполнен по каким-то причинам. URL имеет префикс "http://" или "https://".
  • Result URL - URL (на веб-сайте продавца), на который сервис Web Merchant Interface посылает HTTP POST или SMTP-оповещение о совершении платежа с его детальными реквизитами. URL должен начинаться с префикса "http://", "https://" или "mailto:".

Что делают некоторые разработчики, которые читают эту документацию:
  1. Обрабатывают информацию по одному URL, что приводит к возможности узнать адрес обработчика (также обработчик, в том числе Result URL, может отображаться в платежной форме на сайте WebMoney, но такое происходит не всегда и, вероятно, зависит от настроек).
  2. Некорректно реализуют проверку подписи для запроса, который приходит на Result URL. Это позволяет клиенту подменить данные о платеже.
  3. Проверяют подпись, но не проверяют сумму, которая пришла на Result URL. Это позволяет получить товар за 100$, заплатив, например, 0.01$.
  4. Проверяют подпись, сумму, но не нотацию передаваемых сумм. Помните я упоминал о передаче параметров платежа через браузер клиента? Так вот, WebMoney абсолютно нормально воспринимает сумму платежа со значением 1e1 или 0xFF, а сравнение подобных чисел, еще и на старых версиях PHP, еще и с учетом нюансов сравнения в языке PHP, приводило к самым неожиданным последствиям.
  5. Не совсем проблема платежной системы, НО, как насчет race condition и одинаковых внутренних идентификаторов платежей на ресурсе продавца? Привет, мультипликация баланса.
  6. ...

Подпись запросов​

ocxmyim6js1odi18_pmsgjh5kfs.png
Как это работало:
  1. При оплате через WebMoney, пользователя, в соответствии со спецификацией платежной системы, перенаправляло на сайт WebMoney, где он мог видеть сумму платежа, номер счета и прочие параметры.
  2. После нажатии кнопки “Далее” и аутентификации в системе становилась доступна информация об URL, который отвечал за обработку результата платежа (Result URL).
  3. Пользователь мог сформировать запрос к целевому URL, который, в соответствии со спецификацией WebMoney (ну почти), информировал платежную систему о том, что платеж успешно проведен.
  4. Profit!
ovldtjilil9qydvh6ndq5ovukm0.png

Система приема платежей Global Collect успешно споткнулась о несколько проблем, которые упоминались выше:

  • Известный единый обработчик результатов платежа
  • Отсутствие проверки подписи (да и отсутствие подписи в запросе как таковой)
  • Использование данных, передаваемых через браузер пользователя, в качестве (хотя, согласно спецификации WebMoney, это можно было делать через коллбэк, приходящий от серверов WebMoney)
Все это привело к возможности совершать фиктивные транзакции и покупать все, что использовало процессинг Global Collect, без ограничений. Проблему устранили только через ~2 недели массовой эксплуатации.

Другой вариант похожей проблемы, но чуть сложнее, был не так давно в Smart2Pay.


Еще одна проблема, связанная с подписью запросов - Length Extension Attack.


fngkwrt5plfgngqfylmbci9i3fe.png

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


Ниже небольшая иллюстрация на тему как допустимо генерировать подпись в таком вот стиле и как “выстрелить себе в ногу”.

af1gqv1rk4j2fxhbczdggi3pfjc.png

Для эксплуатации же можно воспользоваться одним из следующих инструментов:

https://github.com/bwall/HashPump

https://github.com/iagox86/hash_extender

Раскрытие “Result URL”​

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

Если выбрать оплату через SMS и ввести случайный недействительный номер, то получаем:


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

u35znqlnl787-r9ojixg1ay3d_o.png

Атрибуты платежа​

Перейдем к проблеме проверки атрибутов платежа.

Один из вариантов интеграции с ЮMoney (ex Яндекс.Деньги), это форма для перевода или ее старая реализация. Опознать ее можно по наличию запросов

https://yoomoney.ru/eshop.xml

или

https://yoomoney.ru/quickpay/confirm.xml

Прямо при отправке запроса необходимо подменить число, которое запрашивает тестируемое веб-приложение:

wtiptduqwjg1g60svn03vknvfhu.png

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

iram94qzfaiurubyn34nykkhuag.png

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

b7zsvwr4zii1uqq7j_59hjeskkm.png

На скриншоте происходит оплата подписки на бота голосового ассистента в Telegram, но сумма платежа не проверяется, что дает приобрести продукт за произвольную цену. Подменяем сумму 1990 на 19, получаем нужную подписку. Этот тип проблем часто встречается (например, много сервисов-ботов в Telegram, о которых многие слышали и где эта проблема до сих пор присутствует), в том числе на зарубежных ресурсах (пример из старых - покупка лицензии Minecraft), в 2022 году, хотя казалось бы…





Еще один релевантный пример, но связанный не с суммой платежа, а с валютой, присутствовал у QIWI. В кошельке была опция пополнения путем отправки SMS на короткий номер, причем валюта передавалась через браузер клиента на нескольких этапах (выбор валюты и суммы, отправка SMS), где сервер доверял данным клиента. В итоге на счет зачислялось 100$, а оплата на 100р.


А что там в 2022?


Смотрим в чат армянского банка
https://t.me/Inecobank_forum/6333

9_r1mj3nygiylz4sk3cwc6f3w3o.png


4m_1wbapyclrhwlqn1hzs1oemuw.png

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


Нотации и сравнение типов​


0vtptotbi0fa9sdbhnwf_dkjov8.png

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

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

Пример возможной логики перевода денег:


q0szifgao_y4l0tfdu1ntqfl7ri.png

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


Другая ошибка - это особенности приведения типов.


7mmi9bsqvszophoosjhptgtu4aa.png

NodeJS - пример языка с динамической типизацией. Когда прибавляешь к числу строку, то произойдет конкатенация 1+"1" = "11" Но стоит из строки вычесть число, то уже строка приводится к числу "11"-1 = 10


Самый популярный формат обмена данными - JSON:


{"amount":100}


Справедливо, что JSON будет корректным: параметр amount с числом 100. Но и этот вариант будет корректным JSON’ом:


{"amount": "100"}


Только содержимое параметра amount будет строкой, но из-за особенностей обработки подобного запроса, возможно кто-то приплюсует к числу 1337 значение этого параметра, и получится 1337100, а не то, что задумывалось изначально)



Ошибки бизнес-логики​

2hqd0kcubva8wgv1gwerrbbd574.png
В ДБО создается платеж, чтобы его подтвердить, необходимо ввести код из SMS. Платеж сохраняется как неисполненный и доступен для редактирования в мобильном приложении. Редактируем платеж, после этого в браузере вводим код подтверждения и итоговый перевод совершается с одной суммой, а со счета списывается другая.


Другой пример:


  1. Вызываем механизм пополнения баланса (на балансе $1000, пополняем на $100).
  2. Веб-приложение запоминает текущий баланс.
  3. В это время тратим (отправляем на второй аккаунт) деньги.
  4. После выполнения транзакции баланс окажется $1100.

Отдельного упоминания заслуживает работа корзины на ресурсах, где используется несколько валют. Уязвимость которая была в магазине Xbox несколько лет назад (причем после исправления появлялась еще пару раз):


  1. Кладёшь в корзину товар за минимальную цену в рублях.
  2. Ищешь в магазине дорогие игры, цены на которые указаны в долларах.
  3. Добавляешь их в корзину.
  4. Магазин считает сумму позиций, но перерасчёт от доллара к рублю происходит в соотношении один к одному.

n4bp6ehg7dlhrdbvccwvw5u4dy8.png

Голоса в vk.com генерировались с помощью SMS со счета с около-нулевым балансом. Отправляешь SMS, оператор связи не может забрать деньги (овердрафт отсутствует), а голоса пополняются.


Другой вектор через SMS - это перевод со своего счета на чужой в платежной системе QIWI. Это делалось через отправку сообщения на специальный короткий номер:



x_y4ycvvpeslatvrizco5zts-qi.png


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

9uozio8ofaxhxs-vxfspn6fuf6q.png
Дальнейшие шаги — использовать сервисы по подмене номера, чтобы отправить туда SMS от аккаунта, на котором много денег. Способ так и не проверен, хотя в теории выглядит крайне забавно). Очевидцы говорят, что подобным образом можно было привязать карту через SMS, а дальше сливать деньги с карты.

А теперь изучим операцию возврата средств (refund). Если рассмотреть каноничный процесс возврата, то станет очевидно, что на каждом этапе можно пропустить или некорректно реализовать все проверки, что приведет к финансовым потерям.


bd8ddnizny3hfybr119xjz3rkba.png

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

Ошибки округления, переполнения и числа с отрицательным знаком​


Частая категория проблем - ошибки округления чисел. Распространенные проблемы с округлением могут выглядят следующим образом:
utg3d-szmocpqtuqlmmhmo6f_uy.png
  1. Пользователь переводит 0,29 RUB в доллары США.
  2. При стоимости одного доллара в 60 RUB, сумма в 0,29 RUB соответствует 0,00483333333333333333333333333333 USD.
  3. Данная сумма будет округлена до двух знаков после запятой, т. е. до 0,01 USD (один цент).
  4. Затем пользователь переводит 0,01 USD обратно в рубли и получает 0,60 RUB.
  5. Таким образом пользователь «выигрывает» 0,31 RUB.

Проблему до сих пор можно встретить в крупных финансовых организациях (различных банках и биржах).
jwkapcawheul1qdyjtv8x-4iccm.png
Если присмотреться, то можно увидеть уязвимость, хоть она и не актуальна на текущий момент. Пусть это тоже будет домашкой.





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


Менее тривиальный пример про переполнения – подсчет суммы заказа при добавлении большого числа товаров в корзину.


igfaioks3rj9zlztdvughd_vn9m.png
Еще один пример – это восприятие больших сумм при передаче между системами. В HTTP-запросе передаваемое число будет строкой, но вот обработка большого числа может отличаться, т.е. отправляется запрос на пополнение на больше чем INT_MAX+2, на локальной системе число обрабатывается корректно, а в платежной системе получаем счет на оплату размером в 1$.


Стоит учитывать, что тестируемая система может использовать не 32-разрядную переменную для хранения значения, а 64-разрядную.


Чтобы лучше понять, можно потыкать циферки в вконтакте. Раньше в vk.com все числа были 32-разрядные. Чтобы получить id1 с помощью переполнения, необходимо было посчитать 2^32+1.


Страница Дурова открывалась под https://vk.com/id4294967297


Но сейчас уже все переведено на int64, поэтому, чтобы получить единицу, необходимо посчитать 2^64+1


Это одни и те же страницы: https://vk.com/id1 == https://vk.com/id18446744073709551617


А теперь представь, что в веб-приложении операцию с id:100 может выполнить только администратор? А если это операция с 2^32+100?





З.Ы. Иногда можно не рассчитать с циферками, и уйти глубоко в минус, так и не достигнув плюса)


itulss3xwkcau7ughb41t0qbhde.jpeg

Состояние гонки​


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


Условно каноничный пример:

xlxhvj5ybr8n1a-4jf-ajsmjzpa.png

  1. Выполняем операцию на перевод средств в рамках баланса.
  2. Совершаем ту же операцию N раз, где баланс должен закончиться при N-1 или больше, но отправляя запросы с минимальной задержкой (тут на помощь приходит HTTP-пайплайнинг, особенности с HTTP2 (все в рамках одной TCP-сессии) и т.п.).
  3. Наблюдаем минус на балансе.

Небольшой пример, связанный с криптовалютной биржей.
mwxgfjyag97k7cf0wbhjnvxvnny.png
Алгоритм эксплуатации был следующий:

  • Создаем тейк-профит на 0,1 BTC, когда стоимость биткойна будет равна $100,000
  • Биржа изымает (блокирует) в аккаунте 0,1 BTC на будующий тейк-профит
  • Удаляем тейк-профит отправляя 438695936458926734 запросов
  • Биржа «возвращает» нам 0,1 x N BTC, где N, количество одновременно выполненных операций

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


Воруем деньги со счетов xss.pro​


Одна из проблем присутствовала на xss.pro в системе перевода BTC между учетными записями.

Для тестирования можно использовать Burp Suite с плагином Turbo Intruder. А подробнее об этой категории проблем можно почитать в статье.

Для этого на депозит кладем 0.1337 BTC, отправляем множество запросов на перевод.


8-fknzlmkbgryi2qlpilw4utgvk.jpeg

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

cl7ck04urxedmpqp4n1dejinsbk.png

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

xswrwlkfhjiuvh25lipk7s_pwnk.png

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

dtrcaj0ludxo_rvco_62iate2yu.jpeg

Я думаю, есть и другие форумы, в которых возможны депозиты и автоматический вывод без ручного подтверждения.




И, пожалуй, самое прикольное:

Скрытое содержимое

Резюме​


Реализация безопасного приема платежей - это комплексная задача, которой должны заниматься опытные разработчики. Получившийся продукт необходимо всесторонне тестировать, иначе мы еще не один десяток лет будем наблюдать детские проблемы безопасности из начала нулевых, особенно при появлении новых классных способов платежей (привет, криптовалюты) и сопутствующих платежных систем. И это мы еще не упоминали атаки на генераторы псевдослучайных чисел, Padding Oracle, и множество других веселых штук, которые заслуживают отдельной статьи.

--
Kaimi & Bo0oM
Прикол в том, что ни у кого такого количества сообщений тут нет для хайда
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх