Поскольку Приватбанк не отреагировал на сообщения с описанием уязвимостей его платежной системы 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