Как сделать честное голосование
Вступление
Хотел назвать «как сделать честные выборы», но уж больно желтый получился бы заголовок =) Хотя на волне текущей обстановки можно подняться.
Написать этот пост меня побудил вопрос на тостере (такой хабровский stackoverflow… легче, видимо, не стало? Тогда забей.) Вот там был подобный вопрос: Нужно сделать вандалоустойчивый механизм голосования на сайте, чтобы можно было защититься от накруток. И мне вспомнился наш опыт в этом деле. А кое-какой опыт у нас за 7 лет работы поднакопился =) Кстати, да! Можешь нас поздравить нам 7 лет, в этом году в школу пойдем. Хочется, конечно, выложить тут алгоритмы и куски кода, которые обеспечат решение задачи, но обо всём по порядку.
История первая: социальная
Помнится некоторое время назад, года 4 уже прошло, нам отдали разработку сайта неофициального сайта ННГУ — ilovenngu. Сам по себе сайт и по тем временам не представлял собой ничего сверх сложного для нас, обычный такой новостной ресурс, пяток страниц и лента новостей. Я же говорю, ничего особо сложного, за тем лишь исключением, что нужно было сделать голосование через соц.сети. Чуть глубже опишу о чем речь: каждый год при поступлении ННГУ устраивает конкурс самых лучших/красивых/умных и т.д. абитуриентов. Приз был то ли iPad, то ли еще что-то интересное, со стороны участников — игра стоила свеч. Не без некоторого обоснования руководство посчитало, что голосование нужно устраивать через соц.сети, по количеству лайков и репостов. Техническая сторона вопроса была описана мной примерно в тоже время вот в этой публикации «Подсчет количества лайков средствами php».
Мы искренне полагали, что этот вариант голосования будет достаточно объективным, были молодые и глупые. Теперь, конечно, понятно, что голосование через соц.сети — это «конкурс друзей и друзей друзей». А сейчас можно совершенно без напряга купить лайки, вопрос гуглится без проблем. Но возвращаемся на 4 года назад и смотрим за развитием ситуации. Выкатываем сайт, начинают поступать заявки, мы их вручную обрабатываем (мозгов не хватило автоматизировать процесс, в оправдание скажу, что позднее мы сделали автоматическое добавление заявок), количество участников конкурса неспешно увеличивается. А потом как-то случайно кто-то из нас нашел, или кто-то из заказчиков нашел ссылку на двач, где шло активное обсуждение нашего конкурса. Как бы это попонятнее сказать, чтобы пост потом можно было показывать детям? В общем, один из участников двача в потоке обсуждения выдал по смыслу следующую фразу: «Господа! Не находите ли вы, что сегодня прекрасный солнечный день? У меня есть отличное предложение помочь несчастной девушке вот в этом конкурсе. Её фотография на фоне персидского ковра просто бесподобна! Она обязано победить!». Конечно, в оригинале вся мысль уместилась в 5 слов: «Зафорсим [женский орган] на фоне ковра!»
Посещаемость достаточно серьезно подросла, не это оказалось проблемой, и вот девочка с ковром резко выбилась в лидеры, я не вспомню какие показатели она выдала, но факт на лицо. Правда, выиграла другая участница, которая тупо купила лайки, что совсем подорвало нашу веру в социальные сети.
Эта история позволила нам сделать вывод, что социальные сети не защищают от накрутки, голоса можно купить за недорого, либо «социальная инжерения» сыграет свою роль.
История вторая: самый крутой модуль опросов
Все персонажи вымышлены, совпадение имен и событий случайны
Решили мы сделать голосование, которое будет обеспечивать защиту от накруток. Представь себе, что сам код идеален и никакой зловред его не поломает, смотри только на логику.
Шаг 1. Защищаемся через ip пользователя. Когда деревья были большими, а ip хватало всем, то очень жизнеспособный вариант, при условии, что все пользователи владеют статическим ip. Но наша вселенная так устроена:
Эйнштейн после смерти попадает на небо и встречать его выходит сам Создатель. Создатель говорит:
— Вот ты и попал к нам, Альберт. Я готов открыть любую одну тайну, спрашивай
— Я всю жизнь строил теорию всего, так расскажи же мне как ты смог это всё создать? Напиши формулу, объясняющую вселенную
Создатель берет мел, и начинает писать на доске огромную формулу. И вот после 3й строки Эйнштейн останавливает:
— Но постой! У тебя ошибка в предыдущем шаге!
Создатель улыбнулся и говорит:
— А я знаю…
Небольшие провайдеры не могут каждому выдать уникальные внешний ip и сажают всех пользователей за NAT, это когда внутри сети ip уникальны, а во внешний интернет смотрит 1 общий ip или небольшой пул разных ip. И вот Вася из под провайдера Далечебург-телеком проголосовал за Свету, и разослал по локальной сети приглашение на конкурс, чтобы все проголосовали за Свету, но вот только ни Коля, ни Оксана проголосовать не могут, сайт почему-то говорит, что они уже проголосовали. Или наоборот, Вася проголосовал, перезагрузил роутер или переподключился к сети и получил новый ip, за статический ip надо денежку заплатить, а зачем? Грусть-тоска.
Шаг 2. Валидация пользователей по сессиям и кукам. Тот же Вася проголосовал и разослал по сети конкурс, все участники локалки тоже проголосовали за Свету. Но вот появился ненавистник Светы — Андрей, она ему отказала во взаимной любви в 10м классе. Он оказался кулхацкером и умеет посылать curl или wget запросы из консоли с определенным набором данных. Этот набор данных интерпретируется как голос за Оксану — любовь Андрея (Ну мы-то знаем, что он так и сохнет по Свете, а Оксана так..) curl откровенно говоря, клал на все эти ваши сессии и куки, и все запросы без проблем проходят. Если Андрей совсем кул, то запросы он шлет ручками, а если уже научился конструировать бесконечный цикл и умеет ставить скрипт на паузу, то за час он накручивает 3600 голосов.
Но я тоже не пальцем деланный, во-первых, у меня есть логи, а если нет, то добавлю! И в них видно, что все запросы идут с одного ip, хм, что же делать? Руками чтоль всё чистить? Не-не-не, Дэвид Блейн, оставь себе свою уличную магию!
Шаг 2+. Если куков нет, то давай проверять связку заголовки+ip+куки и сессии. Ну да, немного усложнит обработку, но нам надо победить Андрея. Допустим, если приходят пустые заголовки user-agenta, то это какая-то лажа, и игнорируем голос, в противном случае смотрю не попадалась ли мне уже где-то такой же набор? Но этот [непечатное обращение], Андрей, научился подделывать и уникальные заголовки и ip и все остальное. Надоело писать триллер, к сути.
Мысль проста, используя для идентификации пользователя только те данные, которые приходят от пользователя, защиту не выстроить.
Шаг 3. «Регистрация!» — подумал Штирлиц.
Зайдем с другой стороны. Нужно идентифицировать пользователя до того как он проголосовал. Логичная мысль, и почему она в голову не пришла сразу? Опустим тот факт, что нужно делать регистрацию, но в данном случае уже тупо post-запросом накрутить голоса не получится. Да, усложняется процедура голосования, но если тебе нужно «честное голосование», то все средства хороши. Всё красиво, но вот подтягиваются наши старые знакомые с двача, и всё идет по бороде. Опять затроллили нашу голосовалку =(
Нельзя просто так взять и сделать голосование честным, вообще без вариантов. Даже если система будет неуязвима, то всегда найдутся энтузиасты, которые за деньги или за идею, сломают всю релевантность опроса. Следствием отсюда является следующий шаг.
Шаг 4. Максимально усложняем жизнь троллям. Голосовать только по подтверждению или через смс. А при чем тут веб?
Давай сделаем обвязку, чтобы проголосовать, прикрутим какой-нибудь сервис по рассылке смс, что-то типа двухфакторной аутентификации, всё вышеописанное + верификационное смс. Правда, лично меня такие темы отталкивают, чтобы проголосовать нужно куда-то сплавить свой номер телефона? Шта?! Тролли, скорее всего, отсеются, а нормальные голоса пройдут.
Фух! Кажется мы победили, если людям не лень регистрироваться в системе, светить свой телефон, то их голоса очень релевантны, но постой, а где вообще голоса? Почему никто не голосует?!
А это цена устойчивости системы от накрутки. Вялотекущее или вообще никуда не текущее голосование. Никто не любит сложности, а уж сложности в опросе «какой ваш любимый цвет» сложности обрекают на забвение.
История третья: из-за кулис
История выдумана, но посыл вполне реален.
Вот приходит заказчик с просьбой сделать сайт и добавить туда опрос. Сайт — просто блог. Плагинов опросов — мильён и маленькая тележка. Берем и ставим самый популярный по запросу «Модуль голосования для…». Заказчик выкатывает голосование: «Какую прическу мне сделать?» с вариантам: растить волосы полгода, побриться под ноль, заплести дреды. А результат голосования должен был стать наказанием за проигранное пари. И неожиданно, все начали голосовать за дреды. Но наш (почему наш? Не, не наш, а вымышленный, да) заказчик не хочет морочиться с дредами, а хочет растить волосы. И теперь он идет к разработчику, либо сам лезет в базу, чтобы поправить результаты.
Что? Честность? Не, не слышал.
Итоги
Очень хочется подытожить всё фразой из другого моего поста про защиту сайтов от взлома. Как сделать честное голосование — никак. Мы живем в таком мире, где честность недостижимая мечта, нужно просто принять этот факт и постараться не нервничать по этому поводу. Это не избавляет от необходимости делать качественный продукт, который защитит от мелкого хулиганства и любопытных глаз. Да и вообще, делать всё хорошо — хорошая привычка.
Всем рок! И еще раз с с днем рождения нас =)