Валидация email адресов в социальных сетях Facebook и Twitter

Валидация email адресов в социальных сетях Facebook и Twitter

Что к чему
В процессе выполнения исследования и тестирования инструментария было интересно, кроме сбора информации о целевых системах, так же выполнить некий базовый сбор информации относительно пользователей той или иной компании. Для этого на входе нам нужен был домен, чтобы собрать email адреса при помощи разных инструментов пассивной и активной разведки, как с самого сайта при парсинге его страничек на наличией адресов, так и из третьих источников с использованием сторонних сервисов и вспомогательных тулов вроде theHarvester.

После сбора адресов выполнялась валидация на сервисе have i been pwned, но оставалось ощущение, что можно выжать "еще чуть-чуть" и отсюда пришло решение попробовать расширить модуль сбора адресов до валидации в социальных сетях. Да, есть уже сервисы вроде pipl и тому подобные, но функционал не совсем устраивал, да и в исследовательских целях платить лишние деньги немного накладно.
[spoiler]

Валидируем email в Facebook

У фейсбука на первый взгляд кажется, что система валидации довольно простая - переходим на страничку восстановления учетной записи, вбиваем туда наш адрес и получаем предположение о том, что "вот возможно ваша учетная запись". Но на деле все оказывается немного сложнее, по нескольким причинам, кроме того, что там есть редиректы, там еще в довесок есть и дополнительные меры защиты от ботов:
  • Проверяется user-agent
  • Нужно выдерживать таймауты или можгут заблокировать на некоторое время
  • Присваивают уникальный токен
  • Записиваю долгоживущую чудесную куку "datr" (у этой куки много задач по идентификации пользователей, залогинен оно или нет)
Порывшись немного в интернетах и на гитхабе был найдем солюшен, но по какой-то причине там не всегда приходил валидный ответ и переодически пролетали ошибки. Наша платформа работает на PHP и при любом раскладе надо было адаптировать его под себя, к тому же, в некоторых случаях проще написать решение под себя, чем адаптировать чужое решение.

Для проверки актуальности механизма валидации пользователей из инструментов подойдет по сути BurpSuite или ZAProxy от OWASP. Все, что нам потребуется - выполнить все действия по восстановлению учетной записи, перехватить запросы и проанализировать их, чтобы в дальнейшем применить подход в своем скрипте. Как результат - нам понадобилось сделать 2 запроса к фейсбуку, чтобы валидировать 1 email адрес (решение к слову, было обкатано на 10+ тысячах адресов).

Первый этап
Сразу мы будем обращаться по ссылке https://www.facebook.com/login/identify?ctx=recover&lwv=110:
function facebookSession() {
    $proxy = "";  
    $user_agent = "";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://www.facebook.com/login/identify?ctx=recover&lwv=110");
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    //curl_setopt($ch, CURLOPT_PROXY, $proxy);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $facebookResponse = curl_exec($ch);
    curl_close ($ch);
    preg_match_all('/\"token"\:\"([a-zA-Z0-9_-]+)\"|\"_js_datr"\,\"([a-zA-Z0-9_-]+)/', $facebookResponse, $matches);
    $res = array();
    foreach($matches as $item) {
        preg_match("/\"token\":\"(.+?)\"/", $item[0], $token); //Забираем token
        preg_match("/\"_js_datr\",\"(.+)/", $item[1], $cookie); // Забираем наш cookie
        $res[] = array(
            'token' => $token[1],
            'cookie' => $cookie[1]);
    }
    return json_encode($res[0]);
}
С помощью первого запроса (функция приведена выше) мы получаем токен и куки, которые в последствии нам потребуются для выполнения следующего запроса - валидации адреса. Хочу обратить внимание, что кука "_js_datr" может жить длительное время, в отличии от токена, который для каждой сессии генерируется по новой. Изначально куку я делал как статический параметр, но к примеру если предстоит выполнить проверки объемной базы адресов, то это не самое лучшее решение и соответственно - более правильно, будет так же получать и ее. С помощью регулярных выражений мы извлекаем без проблем оба параметра и в дальнейшем будет передавать следующей функции.

Второй этап
На втором этапе мы используем полученную информацию и формируем следующий запрос используя эти данные и немного другую ссылку https://web.facebook.com/ajax/login/help/identify.php?ctx=recover:
function facebook($email, $token, $cookie) {
    $proxy = "";  
    $user_agent = "";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://web.facebook.com/ajax/login/help/identify.php?ctx=recover");
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    //curl_setopt($ch, CURLOPT_PROXY, $proxy);
    curl_setopt($ch, CURLOPT_POSTFIELDS,
            "lsd=".$token."&email=".$email."&did_submit=Search&__user=0&__a=1&__req=7&__be=0&__pc=PHASED%3ADEFAULT&dpr=1");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Cookie: datr=".$cookie.";"));
    curl_setopt($ch, CURLOPT_REFERER, 'https://www.facebook.com/login/identify?ctx=recover&lwv=110');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $facebookResponse = curl_exec($ch);
    curl_close ($ch);
    return $facebookResponse;
}
Уже во вторую функцию мы предеаем email который хотим проверить, token и соотвественно cookie. Из полезных мелочей - я бы рекомендовал так же передавать "Referer" и использовать для этого предыдущий URL, чтобы наши запросы выглядели более реально. Как я уже писал, в моем случае не было ни одного бана. По таймаутам - у себя я выставлял на 4 секунды sleep().

На выходе мы должны получить ответ сервера в формате JSON и обработать его, по ответу сразу будет видно - словили ошибку или валидный результат. Если прилетает "error" - то учетки нет, в случае если же в результате будет фигурировать "..ldata.." - значит результат есть и такой email зарегистрирован. Ниже регулярное выражение, по которому я сортировал валидные адреса:
preg_match_all("/.+?(ldata\=)/", $exec, $matches);


Валидируем email в Twitter

С твиттером дела обстоят проще, там есть несколько возможных сценариев проверки, включая возможность использовать их API. Но в случае API я один раз словил блокировку временную (возможно, из-за частоты запросов или их количества, до конца не разбирался).

Запрос для API:
GET /i/users/email_available.json?email=my@email.com HTTP/1.1
Host: api.twitter.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Referer: https://twitter.com/i/flow/signup
authorization: Bearer jl2j4lk2jl3jl23jelj23ejl23kjl2
x-csrf-token: d4840ca54712aa8e2a961
x-guest-token: 108238468704
x-twitter-client-language: en
x-twitter-active-user: yes
Origin: https://twitter.com
Connection: close
Cookie: personalization_id="v1_BvlvvNgdA5YQ1lA=="; guest_id=v1%3A15127074608;
API требует от нас авторизационный токен (Bearer token), с его получением проблем не должно возникнуть, в официальной документации твиттера эти шаги описаны. Остальные параметры не обязательны к передаче.
Я же пошел другим путем и в личном кабинете использовал функцию изменения имени пользователя, в процессе которой так же проходит валидация. Все, что нам нужно передать - это сессионную cookie, срок жизни которой невероятно долгий (пока не закроете сессию). За все врмя использования каких либо ограничений к учетной записи не применялось, блокировок тоже не было (учетная запись подтверждена по email/телефону).

Пример запроса из личного кабинета:
function twitter($email) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://twitter.com/users/email_available?email=".$email."&scribeContext%5Bcomponent%5D=form&scribeContext%5Belement%5D=email&value=m");
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.2 (Windows; U; Windows NT 5.2; en-US; rv:1.8.1) Gecko/200311 Firefox/2.1.0');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Cookie: personalization_id="v1_bphQvnkExg=="; guest_id=v1%3A15453512; _twitter_sess=BAh7CiIKZhJQzonQWNNlcmwrCQFwl2jI8e4O--836cae025b; ct0=c2eb8d19f2d0946911df5; dnt=1; ads_prefs="HBISAA="; kdt=cl1CLDNLmoM3xiq2LWqjNZgH; remember_checked_on=1; twid="u=21"; auth_token=a4769f9b7d00fd4173ff2; lang=en'));
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    $twitterResponse = curl_exec($ch);
    curl_close ($ch);
    return json_decode($twitterResponse);
}
Из минусов такого подхода - нужно передавать больше параметров, в плане скорости - одинаково, по ограничениям - не встречал. В ответе мы получаем опять же JSON формат, если параметр "take" появляется со значением "true" - значит данный id уже забит и соответственно, мы понимаем, что адрес такой - существует. Тут никаких регулярных выражений не требуется, лишь парсить ответ и забирать 1 единственный параметр.
email facebook twitter брутфорс валидация перебор разведка социальные сети
Alt text

Uladzislau Murashka

Разные интересные заметки на тему ИБ и тестирования безопасности, а так же работа в профильной сфере тестирования безопасности в компании ScienceSoft
Кроме основнй работы, так же занимаюсь саморазвитием и исследованиями в сфере ИБ и тестирования безопасности, веду собственный блог на сайте Scan For Security