Демонстрация уязвимостей в 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» в описании. Опять же, это не решает проблему концептуально.

    Прошу не сильно пинать ПБ, поскольку в других платежных системах еще больше багов, но пока мне не до них.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 37

      +19
      Кстати, пинок ПБ сработал — прошло менее часа и первая уязвимость уже пофикшена. Оперативно. Вторую не проверял — больше манипуляций придется сделать
      • UFO just landed and posted this here
          0
          Я и не удивлюсь если ПБ еще и заявление в милицию напишет, уж очень мутная и хитрая контора, приходилось с ними сталкиваться в судах.
          0
          Они давно поняли за чем будующее и куда надо инвестировать ресурсы )
          0
          Интересно, в первом случае API принимает только int в качестве order_id или же можно засунуть что-то вроде Guid?
            +3
            order_id может быть любым набором символов
            +13
            Спасибо за сигнал, меры приняты и уязвимости уже устранены.
              +1
              Вопрос не по теме поста: нельзя никак сделать чтобы счета выставлялись в валюте указанной в магазине, а не в гривнах? Я в курсе что это вроде как требование укр. законодательства, но это очень странное поведение для клиентов: в магазине ценник в рублях/доларах/евро, в платежном интерфейсе ликпея тоже, а в итоге счет выставляется в гривнах.
                +2
                Что ж вы так мало пишете?
                Было бы интересно про айти отдел Приватбанка почитать из первых рук…
                  +9
                  То есть чтобы вы наконец исправили уязвимости понадобилось написать статью на хабр о том как их использовать? Даже не смотря на то что информация вам была передана еще несколько месяцев назад? Серьезно?
                    +1
                    Я думаю, что вы обратились не по адресу. ПБ — большая структура, наверняка та информация даже не доходила до тех, кто может это исправить.
                      +1
                      Я бы не стал доверять структуре в которой такие серьезные вещи не доходят до тех, кто может это исправить.
                        0
                        угу только пот ПБ один из самых больших банков и через него проходят очень очень многие платежи
                  0
                  По второму пункту по-моему проще проверять формат transaction_id, там только цифры, в телефоне тоже только цифры, хотя это конечно только на стороне магазина делать нужно.
                    0
                    во 2м пункте Liqpay вообще не участвует) мы берем подписанный магазином пакет и его же подсовуем, просто подменяя параметры

                    Т.е. я поспешил с указанием как исправить проблему — как ни крути именно эту уязвимость надо блокировать на стороне клиента. Неправильный дизан протокола(
                      0
                      самый простой способ — разрешить принимать колбеки только с ip ПБ серверов, а это не всегда тривиально вычислить)
                        0
                        Потому и написал, что это в магазинах нужно проверять чтобы номер транзакции был числовым (его всё равно лучше сохранять), ну и IP тоже можно, ну или ssl-сертификат.
                    +7
                    Посмотрел недавно API приёма платежей российских платёжных систем. Иногда за такое хочется взять и, извините, уе… уехать подальше от интернета.
                    Такое впечатление, что некоторые мелочи туда добавили прямо специально чтобы добавить граблей в реализацию.
                    Причем неочевидных граблей. Вскользь описанных граблей. Или совсем никак не описанных граблей. Причем у кого три версии API, количество граблей возрастает от версии к версии.

                    Веселее всего у динозара российских онлайн-платежей.
                    Параметр «тестовый режим», который
                    А) не входит в «хеш/дайждест счёта/платежа» (мало кто в здравом уме после прочтения документации этот флаг будет проверять )
                    Б) типа выключается в контрольной панели (на самом деле фиг, флаг в контрольной панели — всего лишь значение по-умолчанию), что нигде не написано
                    B) в любом режиме можно провести платёж описанной на сайте «тестовой» карточкой с номером 4222 2222 2222 2222/CVC123 (выключается, но где-то в дебрях, упоминание есть только в английской версии сайта).

                    Где-то году в 2010 я делал платёжный шлюз webmoney(и др., но этот самый популярный) для биллинга whmcs (и других), по договорённости с первыми пользователями и по сей день я получаю логи обо всех попытках «считерить».
                    За 4 года попыток «считерить» набралось более нескольких тысяч, если просуммировать рублёвые эквиваленты платежей (здесь учтены только те случаи, где мне известна оригинальная сумма платежа), это более 2млн рублей. И это только несколько довольно небольших предприятий.

                    Мораль такова: «Если вы владелец магазина, который принимает интернет-платежи, проверяйте транзакции руками, поскольку некоторые ПС работать головой уже, к сожалению, не в состоянии».
                    Может прямо сейчас у вас за спиной, а возможно и с ведома ваших сотрудников, неустановленные клиенты-физлица выносят, к примеру, диван за 200 тысяч.
                      0
                      Где-то я уже это видел :)
                        +1
                        Это часть большой статьи, которая пока в черновиках по просьбе «неназванной платёжной системы». Пара абзацев возможно даже проскакивала в комментариях или фейсбуке.
                        0
                        Эммм… А что, проверять входные данные и данные отправителя информации из источника, который вы не контролируете, уже перестало быть нормой в разработке???

                        Когда цепляю очередную ПС к сайту, всегда проверяю IP, все поля по регэкспам, и если удается договориться с самой ПС — еще вешаю https+basic auth на соответствующий стык.
                          0
                          Ну большинство нормальных разработчиков так и делает.
                          Догадайтесь, кто обычно «прикручивает платёжную систему» на небольших сайтах, правильно, фрилансер который меньше всех попросил*.

                          *) это не значит, что все фрилансеры, которые мало просят безответственные. Это значит, что таки просто часто попадаются.
                            +2
                            всегда проверяю IP

                            basic auth

                            Такие методы аналогичны использованию пластилина или скотча там, где обязательно нужно использовать саморезы или другие специализированные крепления.
                              0
                              Ну, да, конечно отлично будет, если ПС будет подписывать свои запросы закрытым ключом, со всеми вытекающими. Но левым IP-ам доступа к платежному http лучше не давать, чтобы вас за это место нельзя было особенно заddos-ить. Или я не прав?
                                0
                                И вы так со всеми «third-party» системами поступаете? И как в дальнейшем вы хотите мониторить изменения IP адресов для каждой системы? Это каждому маломальскому проекту понадобится отдельный сотрудник в штате, который этим будет заниматься, и то не факт что всегда всё актуально будет.

                                за это место нельзя было особенно заddos-ить

                                Против этого существуют более эффективные методы. Они так же используют блокировку по IP адресам, как один из факторов обеспечения безопасности, но чёрный список адресов формируется динамически при поступлении подозрительных запросов, и в редких случаях изменяется вручную.

                                Нельзя доверять вообще каким-либо запросам от «third-party» систем. После получения каких-либо запросов в вашу систему, вы должны сами запросить подтверждающие данные у «third-party» системы с определённого белого-адреса. И для такого общения систем часто достаточно лишь HTTPS. Посмотрите к примеру на протокол-авторизации OAuth — это отличный пример, как нужно поступать при реализации всех других схем общения различных систем.
                          0
                          Какие-то ну уж очень «детские» ошибки, которые исправляются все разом — просто добавив на стороне callback-метода проведения платежа отправку запроса в API Liqpay для проверки статуса и суммы платёжной транзакции. Проверив статус и оплаченную сумму по определённой транзакции уже можно наплевать на махинации с подменой параметров.
                            0
                            Вообще магазину, как мне кажется, можно особо не заботиться, о том на сколько круто продуманы подписи создания платежа. Магазину от платёжной системы (ПС) важно только, что бы та позволила пользователю быстро и удобно оплатить определённую сумму. А если пользователь каким либо образом оплатил меньшую сумму, то такие махинации легко нейтрализуются одним лишь запросом проверки статуса и оплаченной суммы от ПС.
                              0
                              Поверьте, никто никогда на стороне магазина не будет делать проверки сразу после колбека. Причем отнюдь не из-за того, что подписанный колбек уже достаточен и не следует переносить свои проблемы на потребителей услуги, а из-за того, что этот функционал никогда, подчеркиваю, никогда не будет запрограммирован — это проверено. Программист со стороны магазина напишет минимально рабочий код, который выполняет задачу и все, дальше не его проблемы, а магазина.

                              И насчет небеспокойства магазинов относительно сверок вы тоже ошибаетесь. Почему платежи проводятся онлайн? Да потому, что и услуга зачастую оказывается онлайн. Вы окажете услугу злоумышленнику, а от банка не получите ни шиша. Так кому беспокоиться?
                                0
                                этот функционал никогда, подчеркиваю, никогда не будет запрограммирован

                                — Не ради пиара, но мы об этом заботимся, т. е. есть всё таки исключения.
                                — Вообще, конечно, на такую печальную действительность наталкивают сами ПС. Другие, «правильные» ПС, предоставляют магазину возможность получать лишь событие об обновлении состояния платёжной транзакции предоставляя при этом лишь номер транзакции (внутренний номер транзакции в ПС). Т. е. такой колбек не говорит о том, что случилось событие успешной оплаты. А уже используя полученный номер, магазину нужно делать запрос в ПС, для того чтобы узнать, в каком состоянии сейчас находится транзакция и все данные о ней. Если бы так поступали не единичные ПС, а все, то программисту просто пришлось бы делать всегда правильно.
                                  +1
                                  у вас плохой опыт. По умолчанию опытный программист реализует спецификацию (и читает ее полностью).

                                  В целом странно, что кто-то проверяет что-то от Liqpay по order id. Для этого есть transaction id, она является идентефикатором платежа и соотв. проверки вокруг нее должны быть.
                              0
                              Небольшой оффтопик, но куда делись номера телефонов, которые возвращались в магазин через API после проведения оплаты?
                                0
                                Из-за таких багов, уже давно снял автоматическое обновление статуса товара, обрабатываю вручную по приходу СМС.
                                  0
                                  Можете ещё одну багу добавить. Или «особенность». Для отправки авторизационных и подтверждающих платежи sms используется шлюз, который запартнерен с парой мессенжеров. Если Вам удастся на номер клиента LiqPay зарегистрировать себе например Viber (нужен физический доступ к телефону клиента на 1-2 минуты), то после этого все авторризационные и платежные SMS от ликпея будут приходить не клиенту на телефон по SMS а Вам на комп/смартфон в Viber. Я им когда-то писал но службе безопасности пофик
                                    0
                                    Я им, кстати, раньше отправлял репорт об уязвимости на главной странице: если заходишь на privatbank.ua, он загружается без SSL, на что мне ответили, что это не уязвимость, а «общие рекомендации по безопасности», потому что «предусмотрена двухфакторная модель аутентификации пользователей [...], что позволяет надежно защитить счета клиентов даже в случае компрометации статического пароля». Т. е. пофиг что пароль угонят, у нас еще один есть? Какой смысл тогда в двухфакторности, если один из факторов вообще бесполезный? Не исправляли достаточно долго, несколько месяцев, сейчас уже исправили, поэтому можно и здесь написать.

                                    Разработчикам на будущее: отправлять данные введенные пользователем через SSL недостаточно, форму тоже нужно загружать по SSL. В ином случае в страницу можно встроить любой JS-код или даже заменить её полностью. Все что для этого нужно — зайти в сети, контролируемой злоумышленником (например, подключившись к Wi-Fi точке в кафе).
                                      0
                                      С последним абзацем верно подметили. Не лишним будет об этом иногда напоминать коллегам-разработчикам. Ведь в наше время даже провайдеры не стесняясь внедряют чужеродные объекты на загружаемые страницы. Тому примеров даже на хабре можно найти много (вот к примеру).
                                      0
                                      Сообщения от магазина платежной системе и обратно от платежной системы магазину подписываются одним и тем же ключом?

                                    Only users with full accounts can post comments. Log in, please.