Демонстрация уязвимостей в Liqpay

в 14:40, , рубрики: liqpay, информационная безопасность

Поскольку Приватбанк не отреагировал на сообщения с описанием уязвимостей его платежной системы Liqpay, я, выждав несколько месяцев, подкреплю тезисы своей предыдущей статьи реальными примерами. Кроме этого, считаю, что пинок будет полезным — я ниже посоветую ПБ как в некоторой степени потушить баги. Возможно, эти уязвимости уже используются, а их характер такой, что страдают конечные пользователи. Заставляя ПБ действовать, будем их (пользователей) выручать.

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

Уязвимость номер 1

Суть: при оплате товара магазина, злоумышленник может изменить данные формы, посылаемые его браузером на api Liqpay, и оплатить иной товар чем был выбран в магазине.

Проблема в формировании подписи: подписываемые данные «склеиваются» без разделителя, в частности параметр order_id и type. Допустим order_id = '1234', а type = 'buy'. Злоумышленник изменяет order_id = '123' и type = '4buy', отправляет на сервер liqpay. При этом валидация подписи будет успешна т.к. строка для ее формирования не изменилась. Вы можете спокойно повторить эти действия в браузере Chrome или Firefox путем редактирование формы на странице — убираете последний символ у ордера и устанавливаете первым в параметре type.

На всякий случай, я немного разжую что произошло, поскольку не все мои товарищи поняли результат уязвимости:

При покупке магазин создает уникальный номер заказа (order_id), формирует форму, подписывает ее и отправляет клиента с этими данными в Liqpay. Когда Liqpay осуществит успешный забор денег у покупателя, он информирует магазин, что такой-то order_id оплачен. Проблема в том, что Liqpay сообщит об оплате совершенно иного ордера, у которого иная сумма для оплаты. Злоумышленнику достаточно создавать неоплаченные заявки до того момента пока у одной заявки order_id не будет иметь фрагмент другого order_id. Поскольку, зачастую order_id формируются не случайным образом, то это возможно в той или иной степени.

Быстрое решение для Приватбанка: просто валидировать параметр type, что не происходит сейчас. Как я отметил, это частный случай, который не решает проблему концептуально.

Уязвимость номер 2

Суть: подписанную магазином форму, предназначенную для оплаты, можно вручную отправить на url callback-а и она будет принята магазином поскольку подпись сформирована по всем правилам. Необходимо лишь сделать небольшие манипуляции:

поле result_url переименовать в transaction_id
поле server_url переименовать в sender_phone
добавить поле status с пустым значением

Этот пакет будет принят магазином!

Вот как считается подпись в пакете НА Liqpay:

private_key . amount . currency . public_key . order_id . type . description . result_url . server_url

А вот как считается подпись в пакете ОТ Liqpay:

private_key . amount . currency . public_key . order_id . type . description . status . transaction_id . sender_phone

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

Для того, чтобы магазин подумал, что это успешная оплата, необходимо немножко больше действий и одно условие:

в описании товара должен присутствовать фрагмент «success».

Наверняка, в магазинах, использующих Liqpay, найдется товар с этим фрагментом. Ну, например, придумаем название книги «My successful story». В сформированной для оплаты форме меняем:

  • от поля description в конце значения отрезаем кусочек текста так, чтобы фрагмент «success» и дальнейший текст не попал в него: description = 'My '
  • поле status = 'success'
  • поле transaction_id = конкатенация текста после «success» и result_url: transaction_id = 'ful storyhttps://blablabla'
  • поле server_url переименовываем в sender_phone

В итоге: подпись не меняется! Магазин принимает пакет. Поле status = «success» — платеж успешен. В поле transaction_id — левый текст — принимается, поскольку магазин не обязан знать как id транзакции формируются в Liqpay (в 99% случаев игнорируют его), sender_phone — левый текст (врядли пакет зафейлится если в телефоне что-то не то, максимум залогируется).

Итог: можно бесплатно «накупить» любого товара сколько угодно лишь бы в описании было слово «success». Урл колбека доступен в форме в явном виде, так что проблем узнать куда слать пакет не будет.

Если вы не поняли смысл манипуляций, то коротко: допустим есть параметры А = ААА, Б = БББ, В = ВВВ. ПО магазина по протоколу Liqpay конкатенирует все параметры в определенном порядке в одну строку АААБББВВВ и получает подпись XXX. Мы можем изменить названия и значения параметров, скажем так: Г = АА, Б = АБББВ, В = ВВ. В итоге сконкатенированная строка та же (АААБББВВВ), а параметры и их значения совершенно иные.

Быстрое решение для «Приватбанка»: блокировать обработку покупок с фрагментом «success» в описании. Опять же, это не решает проблему концептуально.

Прошу не сильно пинать ПБ, поскольку в других платежных системах еще больше багов, но пока мне не до них.

Автор: ef_end_y

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js