Содержание статьи
Идеальной защиты не бывает?
Спешу огорчить: не пытайся читать статью по диагонали или с конца, надеясь увидеть наиболее продвинутый и универсальный способ защиты. Серебряных пуль не существует! Есть ряд мер, помогающих предотвратить накрутку, но профессионалу не составит труда их обойти.
Возникает резонный вопрос: а зачем тогда вообще защищаться? Ответ не менее прост: профессионалов мало и не все они спешат зафейлить твой опрос. В первую очередь стоит бороться с вредителями среднего уровня — пользователями, обладающими небольшой технической подготовкой. Именно они будут активно тестировать защиту твоего опроса в надежде накрутить пару-тройку сотен голосов.
Количество методов борьбы с накрутками ограничивается лишь фантазией разработчика. Они дают разный процент защиты и наиболее эффективны в совместном применении. Для более объективного обзора я отдельно описал минусы каждого из представленных решений. Все приведенные способы защиты не являются магией, поэтому без особого труда реализуются на любом серверном языке программирования. Мне по душе JavaScript на стороне сервера, поэтому все библиотеки привожу с акцентом на Node.js. Для PHP, Ruby, ASP.NET есть альтернативные решения, которые гуглятся без особых проблем.
Делаем ставку на кеш
Давай немного отвлечемся от программирования и вспомним старый добрый протокол HTTP. Не стану выливать на тебя кубометры инфы из спецификации, а попрошу лишь немного вспомнить теорию кеширования.
Браузер постоянно кеширует передаваемые по протоколу HTTP данные, с целью сэкономить трафик, а заодно и время, необходимое на формирование страницы. Картинки, сценарии после первой загрузки будут закешированы, и при повторном посещении сайта есть все шансы, что клиенту не придется заново выкачивать тонну JS-кода, вагон стилей и картинок.
Хорошо, о необходимости кеширования вспомнили, но как оно реализуется? Как браузер понимает, что закешированный ресурс не изменился с момента последней загрузки? В этом ему помогает веб-сервер. Последний добавляет в HTTP-ответы специальные заголовки, по которым браузер и ориентируется.
Протокол HTTP определяет несколько заголовков для кеширования на клиенте: Last-Modified/If-Modified-Since, Cache-control, Expired и ETag. Подробней о них ты можешь прочитать в спецификации, а нас сейчас интересует лишь ETag.
Принцип действия заголовка ETag прост как три копейки. Для передаваемых клиенту ресурсов мы просим сервер помечать эти данные уникальной меткой (ETag). Каждая такая метка — специальным образом рассчитанная уникальная контрольная сумма. Попросту говоря — хеш.
Браузер запомнит соответствие ETag адресу ресурса и при следующем обращении вставит в запрос заголовок: If-None-Match: "хеш". Получив такой запрос, сервер выполнит проверку, и если ETag для запрашиваемого ресурса не изменился, то отправит браузеру ответ с кодом 304 (Не изменен). Для браузера это будет означать, что запрашиваемый ресурс не изменился и можно доставить его из кеша.
Это все замечательно, но как оно поможет в нашей проблеме? ETag предоставляет дополнительную возможность отслеживать уникальных посетителей. Достаточно активировать соответствующую опцию для веб-сервера, и браузер пользователя будет сам себя выдавать при отправке ETag.
Установка ETag активируется либо на уровне веб-сервера (Apache, nginx, lighttpd), либо самостоятельно при формировании HTTP-ответа. Повторюсь, ETag — это просто дополнительный заголовок в HTTP-ответе. Для Node.js ты можешь воспользоваться одноименным модулем. После его подключения формирование ETag сведется к вызову
res.setHeader("ETag", etag(body));
Минусы
Как таковые отсутствуют, но это не означает, что ETag дает стопроцентную защиту в определении уникальных пользователей. Обойти его легко: поднимаем локальный проксик и настраиваем на очистку ответов/запросов от нерадивого заголовка.
Вердикт
Внедрять однозначно стоит. От креативного умника не спасет, но любители будут удивляться и гадать, как этот трюк работает.
Как реализовать
Проще всего самостоятельно. Для любителей серверного JavaScript есть простейший модуль etag (упомянул выше). Фанатам PHP рекомендую посмотреть документацию по функции http_cache_etag.
Капчи, honeypot, токены, IP
Однажды какой-то сообразительный малый предложил защищать формы от автоматической отправки предварительным вводом цифр или иных значений с картинок. Идея проста, но действенна: распознать символы с картинки технически проблематично и дорого, поэтому хочешь или нет, а придется поручить эту работу человеку. Чем изощренней на капче изображены символы, тем сложней их обработать в автоматическом режиме. Главное — соблюсти баланс и не усложнять картинки до такой степени, что их станет трудно прочесть и обычному пользователю.
Сама по себе капча не избавит тебя от учета пользователей (регистрация/привязка к аккаунту соцсети потребуется), но надежно прикроет от армии ботов, жаждущих накрутить статистику. Существует множество реализаций капч: поиск на GitHub по слову captcha выдаст тебе миллион результатов. Но лично я бы посоветовал проверенный вариант от Google — reCAPTCHA 2. Особенность ее в инновационном подходе — пользователю не требуется вводить символы с картинки (ага, зато у некоторых юзеров скоро будут начинаться припадки при виде фразы «найдите витрины». — Прим. ред.).
Вместе с капчей неплохо разместить дополнительные капканы для безмозглых роботов. Хорошей практикой давно служит техника honeypot. Смысл прост — добавляешь в форму голосования несколько «скрытых» полей (то есть полей без визуального отображения) и при обработке формы проверяешь, заполнено ли такое поле. Поскольку поле скрыто, обычный пользователь его не заполнит, а бот этого не знает, он попытается перебрать и заполнить абсолютно все имеющиеся в форме поля. Тем самым выдавая себя.
Напоследок напомню о применении защитных токенов для формы. При создании формы на сервере всегда генерируй для нее токен, который будет передаваться обратно. Если токен зарегистрирован, то стоит обработать результат, а если нет, то наверняка это бот, отсылающий фейки. Наличие токена будет гарантией, что страница была загружена и результаты голосования отправляются именно с нее.
Немаловажно также проверять на стороне сервера IP участника голосования. В этом вопросе стоит проявлять особую осторожность, так как много пользователей могут фактически сидеть на одном IP-адресе. Несмотря на это, никто не отменял фильтрацию отправки по времени. Например, если с одного IP-адреса многократно отправляется форма, то, скорей всего, здесь что-то нечисто и такие запросы следует рубить на корню.
Минусы
Внедрение капчи, honeypot, токенов не спасет от пользователя, желающего проголосовать несколько раз, но хорошо затормозит безмозглых ботов. Это уже неплохая защита, эффективность которой легко расширяется за счет внедрения других техник.
Вердикт
Внедрять стоит однозначно! Защита от ботов не может быть лишней, а ведь именно ими будут пытаться накрутить в первую очередь.
Как реализовать
Готовых капч существует немало, но наибольшую популярность и славу надежной заслужила обновленная reCAPTCHA 2, созданная в компании Google. Для Node.js внедрение заключается в подключении и настройке модуля recaptcha2.
Что касается токенов, то просто гуглим в сторону Cross-site Request Forgery. В проектах для Node.js хорошо себя зарекомендовал модуль csurf.
Сила фронта
Вопрос на засыпку: где на клиенте сохранить информацию, позволяющую точно сказать, что пользователь уже проголосовал? Самый попсовый и ненадежный вариант — cookies. Идея проста: если проверки прошли успешно, то установим пользователю в cookies сформированный на сервере токен. При следующем посещении страницы обязательно проверим, нет ли такого токена в базе. Если есть, то пользователь уже проголосовал и нам неинтересен.
Звучит хорошо, но пользователю ведь ничто не мешает почистить в браузере плюшки и обвести проверку вокруг пальца. Но если способ ненадежен, зачем про него рассказывать? А затем, что он пригодится нам для общей маскировки.
Помимо cookies, нам доступны более продвинутые объекты, позволяющие хранить данные на клиенте: sessionStorage, localStorage и IndexedDB. Ничто не мешает сохранять токены участника голосования в разных местах. Если у пользователя пустые cookies, а в localStorage, наоборот, имеется данный токен, то пользователь наверняка совершил грех накрутки и заслуживает очищающего бана.
Минусы
У пользователя есть полный доступ ко всем перечисленным хранилищам, и он в любой момент может их очистить. Имеет смысл использовать для создания дополнительной линии обороны. Больше помех — выше успех.
Вердикт
Стоит взять на заметку и обязательно реализовать. Далеко не все пользователи продвинуты. Следовательно, есть шансы, что в одном из хранилищ информация может задержаться.
Как реализовать
Взаимодействовать с перечисленными хранилищами просто. Для установки значений используется метод setItem('key', 'value'), а для считывания — getItem('key'). Пара примеров:
sessionStorage.setItem('userId', '12345');
var userId = sessionStorage.getItem('userId'); //123456
localStorage.setItem('userId', '123');
var userId = localStorage.getItem('userId'); //123
С IndexedDB работать чуточку сложней (это все же база данных), поэтому за примером отправляю в MDN или сразу к следующему разделу.
Сила фронта. Копаем глубже
Появление новых хранилищ данных на клиенте — замечательная новость, но, кроме них, есть и другие, более неприметные места: хранилище для Flash/Silverlight-объектов, Java JNLP PersistenceService и так далее. Как ты понимаешь, в них тоже вполне реально оставлять свои метки.
Чтобы не запутаться и не перегореть в попытках наследить во всех тайниках, был создан проект Evercookie. Библиотека позволяет разом записать необходимые данные в несколько источников, а потом так же легко их считать.
Если с Evercookie легко разом «осесть» в нескольких не слишком широко известных местах операционной системы, то библиотека Fingerprint.js поможет быстро узнать уникальный идентификатор пользователя (хеш-сумма), рассчитанный по ряду доступных для получения параметров (поддерживаемые браузером технологии, геозона, список плагинов, User Agent и так далее). Выходит, вместе с отправкой формы голосования надо еще отправлять вот такой идентификатор и уже на сервере определять, был ли голос от пользователя с таким ID или нет. Поскольку Fingerprint.js для работы не требуется использование каких-либо хранилищ, он будет прекрасно работать в режиме инкогнито.
Минусы
Оба проекта чертовски популярны (тот же Fingerprint.js используется в серьезных проектах: MasterCard, AddThis, Baidu и других), а значит, это одновременно плюс и минус. От простых смертных они защитят на ура, но люди в теме смогут их без проблем обойти, так как всегда есть возможность открыть исходники и разобраться с принципом действия.
Вердикт
Проекты аккумулируют множество техник идентификации пользователей, поэтому однозначно достойны внимания. Легкость интеграции и применения — дополнительные аргументы за использование этих библиотек.
Как реализовать
Выполнить две команды для npm и скопипастить несколько банальных строчек кода из документации.
Двухфакторный контроль
Перечисленные выше способы дают определенный уровень контроля, но и его недостаточно. Злоумышленник, желающий подкрутить результаты, рано или поздно разберется с защитой и начнет массированную накрутку. Чтобы еще больше усложнить жизнь хулигану, рекомендую добавить в форму голосований необходимость подтверждения голоса через СМС. Банки, различные сервисы прекрасно используют этот прием во время двухфакторной авторизации.
Идея проста: выбрав в форме голосования понравившийся ответ, пользователь должен ввести номер мобильного телефона, на который придет сообщение с проверочным кодом. Дальше он вводит его в специальное поле, и выполняется проверка. Если проверочный код введен правильно, то засчитываем голос и запоминаем телефон.
Второй раз пользователь при всем желании проголосовать с одного и того же номера не сможет. Чтобы жизнь совсем малиной не казалась, заранее можно предусмотреть пул допустимых номеров. Самый простой вариант — фильтровать по коду страны.
В особых случаях (например, проведение городского опроса) целесообразно отметать номера пользователей из других городов. Да, факапы в таких фильтрах имеют место быть (появилась возможность переноса номеров), но никто ведь не говорил, что будет совсем легко?
Минусы
Голосование должно быть достойно того, чтобы ради него городить такой огород (для разработчика), и иметь для пользователя достаточную ценность, чтобы напрягаться с двухфакторной авторизацией. Обрати внимание, что (спасибо мошенникам) у многих пользователей вообще развилась аллергия на фразу «введите ваш номер телефона» и они закрывают вкладку браузера сразу, как только ее увидят.
Рассылать СМС придется через сторонний сервис. Чем он надежней, тем дороже будет исходящее сообщение. Вопрос надежности требует особого внимания, так как от этого зависит, насколько четко и оперативно СМС будут доходить до адресата.
Согласись, толку от сообщения с проверочным кодом, полученного через сутки после голосования, мало. Следовательно, если планируется большое количество участников голосования, то придется разориться.
Несмотря на общую надежность предложенного метода, обойти его вполне реально. Хакер может потратиться на виртуальные номера и автоматизировать получение проверочных кодов. Раздобыть виртуальный номер с кодом страны (+7) сложней, но все же возможно. Сервисы вроде nexmo подобные услуги предоставляют.
Вердикт
Подключать рассылку проверочных кодов стоит при крайней необходимости.
Как реализовать
Надежных сервисов для отправки сообщений много. Оптимальный вариант — отправка напрямую через оператора. Большая тройка сотовых операторов предоставляет соответствующую услугу (для организаций), и у них реализован адекватный API и примеры использования. Альтернативным вариантом могут быть сервисы вроде sms4b.ru. Это по факту посредники, но у них будут чуть-чуть более привлекательные цены, и с ними проще начать работать — организационные вопросы решаются однозначно быстрей.
Нам помогут посредники
С накруткой давно пробуют бороться таким интересным способом, как перенос голосования со своего ресурса в социальные сети. VK, FB, Twitter и прочие пожиратели рабочего времени позволяют организовывать опросы, тем самым беря на себя и защиту от накрутки и сопутствующих проблем.
Убивается сразу несколько зайцев: программировать ничего не надо и заставлять пользователя регистрироваться тоже. Аккаунты в популярных социальных сетях есть у каждого или почти у каждого. Наличие аккаунта — уже своеобразная защита от накрутки. Плюс появляется возможность сегментировать аудиторию. Например, твой запрос заточен больше на аудиторию Twitter’а, тогда нет смысла заморачиваться позволять голосовать участникам других соцсетей.
Если ни одна из социальных сетей не подходит для проведения опросов, можно организовать сам опрос у себя на ресурсе, но предоставить пользователям возможность голосовать после предварительной авторизации через соцсеть.
Дальше все зависит от твоей фантазии: можно отсекать пользователя по его городу или другим критериям. Например, голосовать могут участники определенной группы, в которую вступают по одобрению модераторов. Способ гарантирует практически стопроцентную защиту, но подойдет далеко не во всех случаях. Если нет активного сообщества — ничего не получится.
Минусы
Пробить можно количеством друзей или фейковыми аккаунтами. С последним можно бороться, вводя дополнительные фильтры (например, город), но говорить с полной уверенностью, что это поможет, нельзя. В Сети навалом всевозможных бирж, где за небольшую сумму можно набрать «добровольцев», которые с радостью проголосуют за нужный вариант, да еще и оставят осмысленный комментарий.
Вердикт
Очевидным «за» будет легкость реализации, а очевидным «против» — распространенность бирж с реальными пользователями, готовыми голосовать за вознаграждение. Дополнительные фильтры помогут отсечь левых участников, но ненадолго. Если требуется быстро запустить голосование/опрос, не доставляя пользователям сложностей и без лишних переживаний о накрутке, — вариант отличный.
Как реализовать
Смотрим инструкцию определенной социальной сети (как организовывать опросы), а при желании прикрутить авторизацию через соцсеть на своем ресурсе обращаемся за помощью к различным библиотекам, API соцсетей и официальной документации для разработчиков. Фанатам Node.js рекомендую присмотреться к passport.js.
Поведенческие факторы
Прекрасной защитой от ботов будет внедрение методов, основанных на поведенческих факторах. Ничто не мешает нам собирать при переходе на страницу с голосованием вот такие параметры:
- время нахождения на странице после ее полной загрузки. Пользователь должен как минимум вдумчиво прочитать варианты опроса, а на это требуется время;
- действия на странице. Скролл (например, до формы с голосованием), перемещение курсора мыши над вариантами ответа, каким образом выполнялся клик (мышкой или клавиатурой) и так далее.
На основании этой информации делаем соответствующие выводы и впоследствии фильтруем пользователей, которых интересовал не опрос, а выполнение чьей-то просьбы проголосовать за определенный вариант.
Минусы
Сложность программирования и анализа. Сбор метрики и написание алгоритма анализа данных потребует времени и хорошего опыта программирования. Если действовать «в лоб», то можно натупить и сбросить с учета голоса реальных пользователей.
Вердикт
Если ты серьезно подходишь к организации голосования, то однозначно стоит продумать стратегию оценки поведенческих факторов участника голосований. Чем больше времени будет этому уделено, тем лучше получится зачистить результаты от накрутки.
Как реализовать
Продумать и прописать на бумаге сценарии поведения пользователя, а затем собрать все необходимое с помощью JavaScript.
Совмещаем онлайн с офлайном
Этот способ актуален для компаний, занимающихся продажей реальных продуктов (журнальчиков или газировки, например). Способ прост до безобразия и успешно применяется различными изданиями, компаниями вроде Pepsi или Coca-Cola. Берем офлайн-продукт и где-нибудь на упаковке печатаем уникальный цифровой код, с помощью которого точно можно идентифицировать пользователя.
Это может быть уникальный набор цифр/букв (вспоминаем конкурсы с призами из-под крышки) или даже современный QR-код. Участник голосования покупает продукт и гарантированно получает один шанс для голосования. Безусловно, потребитель способен закупить целый вагон продуктов или искать бутылки/чеки по помойкам :), но мы понимаем, что на это нужны огромные ресурсы.
В стиле random
Мы рассмотрели несколько вариантов защиты от накрутки. Ты убедился, что добиться стопроцентного успеха в этом деле невозможно. Любая защита имеет слабые места, а раз так, то при желании и наличии времени ее возможно пробить. Все зависит от конечной цели и награды. Если цель голосования — выбрать самую сексуальную сотрудницу ООО «Рога и копыта», а приз — коробка конфет, то вряд ли у кого-то появится желание копаться и анализировать алгоритм голосования с целью накрутки.
Совсем другое дело, если победитель должен получить новенький Range Rover. Тут ценность заметно выше и халявщиков будет больше.
Каждый из описанных способов защиты хорош по-своему. Не стоит выбирать из них самый лучший — его по определению не существует. Сфокусируйся на создании нескольких линий защит. Миксуй различные способы и во время проведения опросов держи руку на пульсе. Постоянно мониторь результаты и анализируй поведение участников. Помни: многие проблемы решить проще, если своевременно о них узнать.
Будут вопросы — смело пиши в комменты! Если возникнет желание прочесть более наполненную кодом статью, смело требуй, и мы ее сделаем :). Просто этот обзор способов защиты от накрутки уже сам по себе объемный: целых четыре полосы, если считать по страницам старого бумажного «Хакера».
WWW
- https://ru.wikipedia.org/wiki/HTTP_ETag — подробная информация о ETag.
- https://www.hurl.it/ — хороший сервис для создания и тестирования HTTP-запросов.
- https://panopticlick.eff.org/ — открытый проект, демонстрирующий определение уникальности пользователя на основании различных критериев.
- https://amiunique.org/ — еще один сервис, позволяющий проверить и идентифицировать пользователя по различным параметрам. Идея такая же, как и у Panopticlick, но в определении «цифрового отпечатка» участвуют другие параметры. Исходный код проекта доступен на GitHub.
- https://clientjs.org/ — достойная альтернатива Fingerprint.js.
- https://habrahabr.ru/company/oleg-bunin/blog/321294/ — доклад автора Fingerprint.js о различных способах идентификации пользователей.
- https://habrahabr.ru/post/244779/ — добротный гайд по интеграции reCAPTCHA 2.