Пользователь
0,0
рейтинг
21 октября 2013 в 00:20

Разработка → Как обмануть Робокассу из песочницы

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

Суть вопроса



Думаю, многие из вас знакомы с таким платёжным сервисом, как «Робокасса». Сервис этот, как водится, работает с двумя типами клиентов: физическими лицами, да юридическими. Рядовой пользователь, покупая нечто в нашем интернет-магазине, ожидает, что ему предъявят счет на сумму, указанную на ценнике. Очевиден тот факт, что требовать от пользователя покрыть еще и комиссию — это прямая дорога вникуда. Вот тут-то и встает вопрос, как переложить обязанность платить робокассе её долю на сам интернет-магазин.

Казалось бы, что может быть проще? Наверняка, такая настройка есть в личном кабинете на сайте платежки. Не тут-то было. Вернее, она есть. Но только в том случае, если вы — юридическое лицо.

В моей ситуации, человек, которому этот магазин создаётся, является лицом физическим. Администрация робокассы предусмотрительно поместила вопрос о комиссии в сайдбар личного кабинета. Видимо, как наиболее актуальный. Дабы не быть голословным:


Я не стану углубляться далеко в подробности о том, как работают транзакции в Робокассе, статья немного не об этом. Если вас интересует техдокументация, она в подробностях разжевана на официальном сайте.
Скажу лишь, что в процессе платежей все держится на нескольких вещах:
  • MerchantLogin — ваш логин в системе
  • InvId — ID выставляемого счета
  • OutSum — сумма, которую мы хотим получить
  • MerchantPass1 — технический пароль №1 для транзакций (всего их два, второй — для получения информации о состояниях платежей)
  • SignatureValue — md5-хеш строки вида «sMerchantLogin:nOutSum:nInvId:sMerchantPass1»


Собственно, любая хитрая смена одного из значений, входящего в строку SignatureValue не даст транзакции совершиться. К слову, Вы, как разработчик можете добавлять свои параметры вида shp*, которые «переживут» платеж и будут отправлены вашему серверу назад. Эти параметры также приплюсовываются к подписи транзакции.
Теперь вернемся к теме статьи.

Решение вопроса



Решение, предлагаемое работниками Робокассы, настораживает сразу же. Выглядит оно так:

Для этих целей создан специальный XML-интерфейс:

Метод расчёта суммы к получению магазином — CalcOutSumm

Описание метода: Позволяет расчитать сумму к получению, исходя из текущих курсов ROBOKASSA, по сумме, которую заплатит пользователь.

Параметры метода: MerchantLogin — идентификатор магазина (строка), IncCurrLabel — метка валюты (строка), для которой нужно произвести расчёт суммы. Если оставить его пустым, то расчёт будет произведен для всех доступных валют, IncSum — сумма, которую должен заплатить пользователь.

Формат запроса: merchant.roboxchange.com/WebService/Service.asmx/CalcOutSumm?MerchantLogin=string&IncCurrLabel=string&IncSum=string


Т.е. нам предлагается высчитывать сумму так, чтобы с учетом комиссии она равнялась цене товара. Магазин писался на рельсах, а потому весь дополнительный парсинг отнял бы несколько строчек. И тем не менее, даже при всём нашем желании

Где зарыта собака?



Проблемы начинаются сразу же, как только мы хотим воспользоваться этим «интерфейсом». Допустим, мы захотели подсчитать сумму для всех способов оплаты. Как гласит руководство:
IncCurrLabel — метка валюты (строка), для которой нужно произвести расчёт суммы. Если оставить его пустым, то расчёт будет произведен для всех доступных валют


Нет. Неправда. Если оставить его пустым — сервер вернет такой ответ (на примере ссылки merchant.roboxchange.com/WebService/Service.asmx/CalcOutSumm?MerchantLogin=demo&IncCurrLabel=&IncSum=3500 )
<CalcSummsResponseData>
    <Result>
        <Code>6</Code>
        <Description>Переданы некорректные значения параметров.</Description>
    </Result>
    <OutSum>0</OutSum>
</CalcSummsResponseData>


Первая мысль: «Возможно я дурак и что-то не так делаю. Может, опускать параметр нужно не так?». Но нет, исходя из той же документации (пример для другой функции, лишь демонстрирую отсутствие значения):
Пример запроса методом HTTP GET:
merchant.roboxchange.com/WebService/Service.asmx/GetRates?MerchantLogin=demo&IncCurrLabel=&OutSum=10.45&Language=ru


Пробуем опустить параметр вовсе:
Missing parameter: IncCurrLabel.


Беда. Но мы не сдаёмся. Что можно сделать в такой ситуации? Точно! Допустим, мы будем брать идентификатор способа оплаты из коллекции, считать для него сумму оплаты отдельно и запихивать в форму на нашем сайте, после чего менять outSum и пересчитывать подпись при выборе пользователем другого способа.

Хорошо, что я не кинулся реализовывать это.

Немного грубого проектирования показало, что на деле всё будет не так уж и радужно. О чём это я? Давайте посмотрим внимательнее на интерфейс инициализации оплаты.

sIncCurrLabel
— предлагаемая валюта платежа. Пользователь может изменить ее в процессе оплаты.


Ничего пока не насторожило? Давайте вдумаемся. Робокасса предлагает нам считать сумму самим, опираясь на выбранный пользователем интерфейс оплаты. Этот самый интерфейс IncCurrLabel в подпись не входит. Это логично, т.к. пользователь имеет право выбрать другой способ на сайте кассы. Тем не менее, комиссия для каждого способа высчитывается своя. Более того, высчитывать её предлагается нам, на стороне нашего сервера. Мы получаем outSum от того самого интерфейса, запихиваем в нашу форму, считаем подпись и отправляем на оплату.

Суть всей статьи

Ещё раз.
Робокасса предлагает нам вычитать из нашего дохода сумму комиссии, основываясь на том, какой способ оплаты хочет пользователь. При этом, этот самый способ оплаты она дает менять тогда, когда мы контроля над процессом платежа уже не имеем. Что происходит дальше?

А дальше все просто. Пользователь выбирает на нашем сайте способ с самой большой комиссией. На моей памяти — банковская карта. Мы, как добрые дяди, вычитаем порядка 300 рублей из цены нашего товара, дабы снять ношу комиссии с покупателя. Он же, попав на сайт Робокассы, просто выбирает оплату через какой-нибудь Яндекс или Вебмани с мизерной комиссией. Комиссия по новому способу высчитается на сайте робокассы опираясь на отправленный нами «скидочный» вариант цены. Всё.




И всё-таки, загвоздка получается в том, что с момента попадания на сайт платежки если пользователь оплатит заказ — нам вернется «успех» по платежу. И никого не волнует, что мы потеряли деньги на этом, по сути. Такая вот нехитрая схема.

Что всё-таки можно сделать?



Выход номер раз

Зверский

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

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

Выход номер два

Единственный

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

Выход номер три

Несуществующий

Робокасса могла бы сделать возможность запрещать пользователю менять способ оплаты после инициализации оплаты. Например, ввести флаг canChangeCurrLabel, да пихать его в подпись транзакции. Тогда интерфейс расчета стоимости приобрел бы смысл, а мы не теряли бы деньги. Что помешало — неизвестно.
@heidar_ice
карма
11,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (31)

  • –1
    А робокассе то это сообщили, точнее прошло ли какое-то время после уведомления? А то античат получается.
    • +1
      Нет, никакого античата, в робокассу запрос направлен.
  • 0
    В shp параметрах там тоже небольшая дыра. Из документации (ссылка), для оплаты, пользователя переносят на сайт робокассы post-запросом. Одним из данных формы является md5-подпись, которая берется от строки «sMerchantLogin:nOutSum:nInvId:sMerchantPass1:shpa=yyy:shpb=xxx». Казалось бы, при заданных параметрах, магазин делает md5 подпись, и мы никаким образом не может составить другую валидную подпись для других параметров. Однако это не совсем верно. Если магазин запихивает в shp-параметры какие-то важные параметры (как например «оплаченную сумму»), воздействовать на них можно так: допустим shpb=1000. Вы тупо добавляете лишний 0, и получаете строчку с shpb=10000. Далее обновляете существующую md5-подпись, добавлением 0 (алгоритм генерации md5 позволяет добавлять в строчку символы). Если на стороне магазина скрипт не проверяет, что же там было в присланной «OutSum», то налицо вы можете пополнить счёт на произвольную сумму.

    Подобный недочет со стороны робокассы можно исправить либо заменой md5 на другую функцию, либо же простым банальным переставлением sMerchantPass1 в конец строки.

    Поправьте меня, если я написал чушь.
    • 0
      «Далее обновляете существующую md5-подпись, добавлением 0 (алгоритм генерации md5 позволяет добавлять в строчку символы). „
      Это как это вы сделаете, не зная, sMerchantLogin и sMerchantPass1?
      • –1
        MerchantLogin-то, кстати, передается в запросе в открытую. Пароль, разумеется, скрыт. Хотя, такой подход вообще говорит о том, что технические пароли (MerchantPass1 и MerchantPass2) хранятся в БД плейнтекстом. Да и вообще, здорово это, количество фактических параметров запроса — на ладони, алгоритм формирования строки для подписи — вот он тут. Строка шифруется без соли, без всего, обычным одним проходом md5. Не внушает мне всё это доверия. Хотя я, возможно, параноик.
        • 0
          Вы не параноик. По крайней мере, этого не следует из вашего комментария.

          Вы просто не понимаете, что это не шифрование, а цифровая подпись. В нее включено значение sMerchantPass1 или sMerchantPass2 (зависит от типа запроса к робокассе). Эти два значения достаточно длинны для попытки их перебора. Они известны только магазину и Робокассе, и не передаются при обмене сообщениями. То, что они хранятся «плейнтекстом» — это так и должно быть. Сгенерировать без них новую подпись — невозможно за приемлемое время. Любое изменение в данных (даже порядок передачи в GET-запросе) приведет к несоответствию запроса его подписи.
      • +1
        Для того, чтобы из строчки s+'0' сделать md5, нужно знать только md5 от строчки s.
        • +1
          Меня смущает фраза «алгоритм генерации md5 позволяет добавлять в строчку символы» то ли я что-то не знаю, то ли вы что-то путаете. Я всегда считал что это не так.

          Можете на конкретном примере показать?

          Например:
          md5(1) = c4ca4238a0b923820dcc509a6f75849b
          md5(10) = d3d9446802a44259755d38e6d163e820
          md5(c4ca4238a0b923820dcc509a6f75849b0) = 8e0e21c3a3888fbb6c04d55a20459d3c
          • 0
            from hashlib import md5
            
            m1 = md5('10000')
            print m1.hexdigest() 
            # b7a782741f667201b54880c925faec4b
            
            m2 = md5('1000')
            m2.update('0')
            print m2.hexdigest() 
            # b7a782741f667201b54880c925faec4b
            
            • 0
              А вас не смущает что m1 и m2 — объекты, которые, вероятно, хранят исходную хешируемую строку, что позволяет выполнять метод update()?
              • +3
                Вики, хабр (секция про flickr)
                • 0
                  Да, я по-прежнему опасно некомпетентен в криптографии.
                • 0
                  Огромное спасибо за разъяснение!
              • 0
                В данном случае вы правы, m2 хранит состояние.
                Однако имея и конец хэшируемой строки, и конечное состояние md5 мы можем «откатить» внутреннее состояние md5 до того места, где мы сможем вставить новые данные. Таким образом мы можем получить новый хэш, не зная секретный префикс.

                ОДНАКО! Это не позволяет добавить символы в строку не изменив MD5.
                • 0
                  Да, я уже понял. Прочитал ссылки из комментария vinograd19 от 21 октября 2013 12:29 про Length extension attack.
          • 0
            Например вот: github.com/danghvu/md5pad, это назвается Length-Extension Attack.
        • 0
          У md5 есть такая уязвимость, о который вы говорите. Но ее практическое применение, тем более в каком-то интернет магазине — фантастика.
    • 0
      Вы написали чушь. Каким образом вы собираетесь изменить этот самый md5 хэш не зная sMertchantPass1?
  • +7
    В смысле вы потеряете деньги из-за махинаций пользователя? Вы в любом случае получаете не меньше, чем готовы были получить, предоставив пользователю скидку на размер комиссии. Ну и что, что пользователю товар обойдется чуть дешевле при смене способа оплаты — вы то получаете всю сумму, которую готовы были получить по самому дорогому способу. Ну а сообразительному клиенту можно только поаплодировать за догадливость.
    Странные люди — согласны на поступление выручки 100 руб если клиент заплатит комиссию 10 руб, но не согласны на 100 руб. если клиент заплатит комиссию 1 руб. В данном случае, клиент за счет сообразительности получит бонус 9 руб СВОИХ сэкономленных денег. Поймите, чем больше у вас довольных клиентов, пусть даже при таком нежданном бонусе, тем лучше для вашего бизнеса.
    Вы же все равно работаете в плюс, ну а если вдруг получилось в минус, то долбайте своих маркетологов или кто там у вас цену считает.
    • 0
      На сколько я понял, речь шла о том, что после смены клиентом способа оплаты магазину придётся заплатить Робокассе бОльшую комиссию, чем рассчитывалось при генерации цены.
      • 0
        Нет, неправильно. Комиссия, наоборот, меньше.
    • 0
      Мой интерес здесь вовсе чисто технический, как разработчика. Заказчик устанавливает цены на свой товар для продажи в магазине, я создаю такой магазин, в котором возможно реализовывать этот товар по заданным ценам, однако ограничения в самой платформе не позволяют сделать это должным образом. Вот и всё. А сообразительные пользователи — это вообще прекрасно.
    • +2
      А если запустить вирусную рекламу, что этот магазин можно обдурить таким вот нехитрым способом, то у потенциальных покупателей появится крайне заманчивый мотив купить что-то в этом магазине. Народ у нас падкий на халяву, но на возможность обдурить еще больше.
  • +10
    9% при оплате банковской картой, это как то жирно, вам не кажется? Робокасса всегда была жадной…
  • +3
    Я читал, что при использовании Робокассы бросают заказ порядка 70% покупателей.
    Во-первых из-за увеличения суммы (набрал на одну сумму, а платить надо больше)
    Во-вторых из-за недружественного интерфейса самой робокассы, люди в нём просто теряются.

    Пока смотрю в сторону единой кассы. у кого есть опыт работы с ней или другими платёжными агрегаторами?
    • 0
      Кстати тоже очень интересуют другие платежные агрегаторы.
      смотрю еще:
      sprypay
      paysto
      • 0
        Работаю с Payonline. Небольшая комиссия, продуманное api(нравится, что также можно отправлять дополнительные параметры, которые потом вернуться по callback).
    • 0
      Личный опыт — за последние 4 года раз 10 платил через РК. И 2 раза в РК «Произошла ошибка», в банке «пули вылетели», в магазине — «счёт не оплачен». А дальше разоборки со всеми сторонами с пинг-понгом: банк делает покерфейс, РК старательно делает вид что техподдержки у неё нет вообще.

      Мой нерепрезентативный опыт — 20% проблем при оплате и 100% отсутствия техподдержки. Теперь оплачиваю только через другие системы.
  • 0
    Я правильно понимаю, что такая «проблема» возникает только если добровольно пытаться взвалить на «себя» бремя комиссии в процессе расчета стоимости?
    • 0
      Абсолютно верно.
      • 0
        Но, с другой стороны, если мы взваливаем на себя комиссию, то должны быть морально готовы к тому, что придется возмещать самую большую из возможных (по закону Мерфи), а если возмещаем меньшую — это можно считать приятным бонусом.

        Отсюда правило © — чужие риски ведут к дополнительному «попадалову».

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.