Мои знакомые постоянно участвуют в различных конкурсах. При этом они привлекают к этому всех своих друзей и знакомых, рассылая в аське или вконтакте просьбы «Проголосуй за меня тут», «Проголосуй за фото» и т.д. Думаю, такая ситуация знакома многим. И вот однажды я решил помочь одной своей подруге и вот что из этого получилось.
Внимание! Данный пост написан исключительно для ознакомления. Используйте данную информацию только для разработки методов борьбы с накруткой при разработке собственных приложений. Играйте честно, друзья, и не нарушайте закон!
Для голосования не требовалось регистрации. Единственное ограничение было в том, что с одного IP можно голосовать не чаще, чем раз в 3 часа. Это значительно облегчало задачу. Первым делом я полез в куки браузера, чтобы почистить их. Конечно, вероятность такой халявы была очень мала, но я всё таки попробовал:) Очевидно, что IP адрес и время последнего голоса хранятся на сервере. Значит мы будем работать со списком прокси-серверов, чтобы обойти это.
Если в двух словах, то нам нужно определить, какие данные посылаются на сервер при нажатии на кнопку «Голосовать», и потом автоматизировать это, чтобы такие же данные отсылались быстро, часто и без рук:) По мере сложности нам придётся делать дополнительные действия перед отправкой данных. Всё зависит от того, что там для нас приготовили разработчики модуля голосования…
Откроем исходный текст страницы с голосованием. Нас интересует тэг <form>
,
его атрибуты action
и method
, а так же все вложенные тэги <input>
. Судя
по всему это модуль aPoll для Joomla. Но на самом деле это не важно, потому
что я опишу тут общие принципы, которые применимы для любого подобного
механизма голосования.
Всё содержимое формы будет отправлено на сервер по нажатию на кнопку
type=submit
. Способ отправки данных формы на сервер указан в атрибуте
method
. Для отправки данных через форму используют методы GET
или POST
(в протоколе HTTP существуют ещё и другие методы, о которых вкратце можно
почитать, например, тут). Данные уходят
по адресу, указанному в action
, в виде пар key=value
. Если клиентская
сторона веб-приложения реализована с применением ajax, то определить метод и
адрес будет немного сложнее, но дальнейшие наши действия почти не будут
отличаться (возможно, я расскажу об этом следующий раз).
Назначение почти всех <input>
понятно. В глаза бросается только последний
неведомый параметр:
<input type="hidden" name="dd972dd7998aca5da8349c281a530424" value="1" />
Если обновить страницу, то имя этого параметра изменится.
Поскольку используется метод POST
, я предполагаю, что запрос будет выглядеть
примерно так:
POST /path/to/voting/script HTTP/1.1
Host: www.target.ru
Connection: keep-alive
Content-Length: 89
Content-type: application/x-www-form-urlencoded; charset=UTF-8
Accept: text/html
voteid=83&option=com_apoll&id=20&format=raw&view=apoll&dd972dd7998aca5da8349c281a530424=1
Чтобы получить точные данные, которые посылает браузер, я воспользовался Wireshark`ом. О снифферах и особенно о wifi-снифферах я расскажу подробнее в следующий раз.
Запускаем Wireshark, настраиваем фильтр так, чтобы отлавливались только пакеты
от нас до нужного хоста и обратно (лишний мусор нам ни к чему). Ещё можно
отключить картинки в браузере - это уменьшит объём трафика между нами и
сервером и улучшить читаемость списка отловленных пакетов. Удаляем в браузере
куки для этого сайта, открываем страницу с формой голосования, выбираем нужный
пункт, нажимаем «Голосовать». Останавливаем захват пакетов. Ищем пакет POST
:
Тут всё, как я и предполагал, за исключением строки:
Cookie: 0c8b34dacf4f14182867180a1aaba5c4=47e0e642a25f8b86d926b6f2cf9eef55
Теперь понятно зачем нужен тот неведомый параметр со страшным именем.
Это простейшая защита от накрутки, которая лишь наполовину усложнит наш скрипт
- нельзя тупо отправить POST-запрос на сервер, не загрузив перед этим страницу
с голосованием. Откуда нам получить эту куку? Ищем пакет GET
с запросом
самой страницы. Следом за ним ищем ближайший HTTP/1.1 200 OK
:
Из него нам нужна только строка Set-Cookie
. Это указание браузеру сохранить
куку key=value
для текущего сайта, а точнее для его части, указанной в
path
(в нашем случае - это корень).
Теперь все звенья цепи собраны, можно начинать!
Общая картина у нас теперь сложилась. Нам нужно запросить GET
страницу с
формой голосования, вытащить из заголовка куку и тот неведомый параметр
из формы. Затем сформировать POST
запрос с кукой, неведомым
параметром и другими параметрами, которые остаются неизменными. Всё это нужно
выполнять в цикле по файлу со списком прокси-серверов.
Выполнение скрипта будет занимать много времени - соответственно, его нельзя будет расположить у какого нибудь веб-хостера. Никаким шеллом я, к сожадению, не владею. Поэтому запускать буду на локальной машине:) Это крайне небезопасно, и я никому не рекомендую так делать!
Где достать список прокси? Для меня это тоже было проблемой. В сети много
мест, где бесплатно публикуют списки прокси-серверов. Половина из них просто
не работает. Две трети оставшихся ни разу не анонимны, но для наших целей
вполне подойдут, так как всё же меняют REMOTE_ADDR
. По началу я проверял
найденные списки в программах прокси-чекерах. Но потом забил на это, потому
что написанный скрипт сам переходил к следующему прокси из списка, если
текущий не отвечал и вываливался по таймауту. Вид списков прокси серверов
может быть разным: где-то адрес и порт могут разделяться двоеточием, где-то
пробелами или табуляцией. В таком случае придётся подогнать разбор строк в
скрипте под нужный формат записи.
После вечера кодирования и отладки у меня получилось так:
<?php
function PostRequest($url, $proxy_host, $proxy_port, $data, $cookie)
{
$url = parse_url($url);
$host = $url['host'];
$path = $url['path'];
$fp = fsockopen($proxy_host, $proxy_port);
if (!$fp)
return;
fputs($fp, "POST $path HTTP/1.1\r\n");
fputs($fp, "Host: $host\r\n");
fputs($fp, "Connection: close\r\n");
fputs($fp, "Referer: $url\r\n");
fputs($fp, "Accept: text/javascript, text/html, application/xml, text/xml, */*\r\n");
fputs($fp, "Content-Length: ".strlen($data)."\r\n");
fputs($fp, "Origin: $host\r\n");
fputs($fp, "X-Requested-With: XMLHttpRequest\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded; charset=UTF-8\r\n");
fputs($fp, "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 Safari/534.13\r\n");
fputs($fp, "Cookie: $cookie\r\n\r\n");
fputs($fp, $data);
$result = '';
while(!feof($fp)) {
$result .= fgets($fp, 128);
}
fclose($fp);
$result = explode("\r\n\r\n", $result, 2);
$header = isset($result[0]) ? $result[0] : '';
$content = isset($result[1]) ? $result[1] : '';
return array($header, $content);
}
function GetRequest($url, $proxy_host, $proxy_port)
{
$url = parse_url($url);
$host = $url['host'];
$path = $url['path'];
$fp = fsockopen($proxy_host, $proxy_port);
if (!$fp)
return;
fputs($fp, "GET $path HTTP/1.1\r\n");
fputs($fp, "Host: $host\r\n");
fputs($fp, "Connection: close\r\n");
fputs($fp, "Accept: text/javascript, text/html, application/xml, text/xml, */*\r\n");
fputs($fp, "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 Safari/534.13\r\n\r\n");
$result = '';
while(!feof($fp)) {
$result .= fgets($fp, 128);
}
fclose($fp);
$result = explode("\r\n\r\n", $result, 2);
$header = isset($result[0]) ? $result[0] : '';
$content = isset($result[1]) ? $result[1] : '';
return array($header, $content);
}
$handle = @fopen("C:/proxylist.txt", "r");
if ($handle)
{
while (($buffer = fgets($handle)) !== false)
{
$result = explode("\t", $buffer, 3);
$proxy_host = isset($result[0]) ? $result[0] : '';
$proxy_port = isset($result[1]) ? $result[1] : '';
$proxy_port = str_replace("\n", "", $proxy_port);
echo "$proxy_host : $proxy_port - ";
if ($proxy_host != '' && $proxy_port != '')
{
list($header, $content) = GetRequest(
"http://www.target.ru/road/to/hell",
$proxy_host, $proxy_port
);
if (preg_match('/Set-Cookie: (.+);/', $header, $matches))
{
$cookie = $matches[1];
if (preg_match('/<div><input type="hidden" name="(.+)" value="1" \/><\/div><\/form>/', $content, $matches))
{
$fuck = $matches[1];
echo $fuck;
$data = "voteid=83&option=com_apoll&id=20&format=raw&view=apoll&$fuck=1";
list($header, $content) = PostRequest(
"http://www.target.ru/road/to/fucking/hell/fuck/yeah",
$proxy_host, $proxy_port,
$data, $cookie
);
echo " - OK\n";
}
else
{
echo "Fuck fail\n";
}
}
else
{
echo "Cookie fail\n";
}
}
}
fclose($handle);
}
?>
Не самый красивый код, но главное работает :)
UPD (21.03.2011): На сайте всё таки зачесались - сделали обязательной регистрацию для голосования :) Я переделывать скрипт не буду. Свою задачу он сделал, когда это было нужно. Требование регистрации усложняет задачу на порядок, но принцип накрутки всё равно останется таким же.
© 2011-2022 - Станислав Спиридонов