В ноябре прошлого года компания «Яндекс» провела конкурс на тему поиска уязвимостей в своем сервисе. Мне посчастливилось найти там пару дырочек и получить за это второе место. Так как за эти полгода я так и не опубликовал деталей (кроме как на встрече Defcon-Russia, но это было в устной форме для узкого круга посетителей), я решил восполнить этот пробел сейчас. Так что тут будет рассказ об одной из дырок, которая была обнаружена в рамках конкурса и оперативно закрыта компанией «Яндекс». Считаю, что конкурс полностью оправдал себя и позволил предотвратить страшные последствия, так что идея явно удачна, одни плюсы. Собственно рассказ будет о банальном отсутствии проверки авторизации в одном из скриптов, что могло привести к частичной компрометации более миллиарда писем лишь на одной ноде…
Начало
Когда конкурс был объявлен, мне, как руководителю департамента аудита в Digital Security, не хотелось участвовать в этом (я вообще конкурсов боюсь, да и начальники имеют другие важные дела — приказ там отдать кому или отчет потребовать… 8)), но, как всегда, любопытство и жажда наживы продиктовали свои условия. Я решил поискать что-нибудь интересное и веселое, поэтому решил не тратить времени на поиск таких уязвимостей, как XSS и CSRF. Во-первых, они находятся автоматически при изучении любого скрипта (собственно, пара XSS’ок нашлась сразу). Во-вторых – неинтересно(хотя и опасно, а как показал kyprizel в том же конкурсе). Кроме того, так как зона конкурса (scope) была гигантской, я решил ограничиться только самым интересным для меня сервисом – почтой. При этом решил не тратить более одного рабочего дня, чтобы не отвлекаться от работы. Это позволяло мне не беспокоиться, что я ничего не найду, ведь времени нет (самооправдание, ага!), и искать что-нибудь действительно угарное. В любом случае, для начала надо было просто изучить структуру и логику работы веб-интерфейса. Делалось это банально – с помощью BurpSuite и встроенного в Google Chrome интерфейса разработчика.
Антон Карпов aka toxa (tokza) рассказывает, как будут оцениваться найденные баги.
FrontEnd
Основным скриптом по доставке контента почты является некий handlers.jsx. Примечательно, что информацией по архитектуре почты сами разработчики охотно делятся, так что мы знаем, что в данном случае имеем дело с серверным JavaScript. Однако мне все эти тонкости не понадобились, так как первая же найденная там уязвимость оказалась настолько классической, что детали реализации не так уж и существенны. Но сначала общие детали:
- Запрос к handlers.jsx может быть как POST, так и GET
- В запросе указывается нужный модуль и соответствующие ему параметры
- В ответ на запрос приходит контент в виде XML
Некоторые модули и их смысл:
- “folders” – содержимое папок
- “message-body” – тело письма
- “nearest-messages” – соседние письма
Чтобы было нагляднее, приведу скриншот из той же презентации Алексея Андросова.
То есть видно, что весь контент собирается в общий интерфейс с помощью AJAX-запросов и приводится в «человеческий» вид благодаря XSL-шаблонам.
Ну и пример запроса из той же презы:
/handlers.jsx?_handlers.0=message&ids=1234567890&_handlers.1=message&ids=1234567891
Ответ в виде XML:
<handlers>
<handler name=”message”
key=”_h=message&ids=1234567890”>
<!-- данные -->
</handler>
<handler name="message"
key="_h=message&ids=1234567891">
<!-- данные -->
</handler>
</handlers>
Вроде все просто. Параметр _handlers определят нужные нам данные (модуль обработчика), а ids – это идентификатор. То есть данный пример запроса загружает письма с идентификаторами 1234567890 и 1234567891. Естественно, первое, что попробовал я – это подменить ids и подставить туда, например, 123431337, что, в идеале, должно было бы вернуть мне письмо с этим идентификатором, даже если оно не мое.
У меня ничего не получилось. Кстати, то же самое действие предпринимал и мой коллега и друг — chipik. В прошлом году мы с ним на пару уже ломали Google (и мы писали о том, как это было). Но и у него тоже ничего не получилось. Однако вот ведь забавная штука: есть такое понятие – «покрытие кода». То есть, предприняв некое проверочное действие, мы проверили часть кода. Логично предположить, что если попробовать подменить имя модуля, то все равно за проверку авторизации доступа по идентификатору ids отвечает тот же самый код. Но небольшая удача помогла понять, что это не так.
Бага
Через какое-то время я опять решил подменить ids, но уже для другого модуля – “nearest-messages”, и о чудо… Я сначала даже не понял, как это произошло, но позже убедился, что это единственный модуль, который не проверяет, принадлежит ли ids письма текущему пользователю. Это означает, что с помощью этого модуля я мог подгружать «соседние письма» других пользователей сервера! Давайте разберем легитимный запрос:
http:// mail.yandex.ru/neo2/handlers/handlers.jsx?_handlers=message-nearest&ids=2020000002590081157
2020000002590081157 – это ids моего письма, вполне конкретного и определенного. На этот запрос сервер Яндекса возвращает:
(... – вырезано для сокращения объема)
<handlers>
<handler name="message-nearest" gid="0" key="_handler=message-nearest&ids=2020000002590081157">
<message-nearest>
<list>
<message id="2020000002590182740">
<thread id="2020000001499988536"/>
<folder id="2020000650059912118"/>
<date>
<timestamp>1320143331000</timestamp>
<iso>2011-11-01T14:28:51</iso>
<short>01.11.11</short>
<full>1 ноя. 2011 в 14:28</full>
</date>
<size>2 КБ</size>
<last_status/>
<subject fwd="Fwd:">TEST</subject>
<firstline>Привет, это тестовое письмо номер 2</firstline>
<to>
<name>ne_eik01110d111@yandex.ru</name>
. . .
</to>
<from>
<name>SSS AAA</name>
. . .
</from>
…
</message>
<message id="2020000002590081157">
<thread id="2020000001499929723"/>
<folder id="2020000650059912118"/>
. . .
<size>1,8 КБ</size>
<last_status/>
<subject re="Re[2]:">TEST</subject>
<firstline>
Здорово, тестовое письмо…
</firstline>
…
</message>
<message id="2020000002590076913">
<thread id="2020000001499929723"/>
<folder id="2020000650059912118"/>
. . .
<full>1 ноя. 2011 в 14:01</full>
. . .
<subject>TEST</subject>
<firstline>Сообщение 1</firstline>
<from>
<name>Alexey Sintsov</name>
<email ref="3cab8cda1a132a2d17803cc47cd55d91">ddookie1@inbox.ru</email>
</from>
<reply-to>
<name>ddookie1@inbox.ru</name>
<email ref="3cab8cda1a132a2d17803cc47cd55d91">ddookie1@inbox.ru</email>
</reply-to>
. . .
</message>
</list>
<timer_db>266</timer_db>
<timer_logic>175197</timer_logic>
</message-nearest>
</handler>
<login>ne_eik01110d111</login>
<actual-version>5.11.55</actual-version>
<timestamp>1335882564270</timestamp>
</handlers>
Сервер возвращает XML с тремя объектами. Каждый объект – это информация о письме:
- Когда
- От кого
- Кому
- Заголовок
- Первая строчка тела письма
Причем то сообщение, ids которого указывается в запросе, идет вторым объектом. Первый и третий объект – соседние сообщения в той же папке, где находится запрашиваемый объект. Меняя ids, например, на единицу, мы получаем три чужих сообщения. Вернее, дату получения, заголовки, адресаты, идентификатор ids и первую строку письма. Но на самом деле первая строка – не просто первая строка! Опыт показал, что туда может входить несколько строк, и главное тут – объем и количество переводов строки. В моих опытах были результаты и по 5 строк! То есть объем достаточно большой, чтобы говорить о реальной угрозе.
Отправлено в теле письма:
Привет – строка 1
Ага, это тесто – строка 3
Ха-ха-ха-ха-ха-хавыавыаывавыавыавыавыавыавыа – строка 4
фыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыы – строка 5
фыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыфыф – строка 6
Пока – 8
Твой тестер! – 9
Перехвачено с помощью handlers.jsx:
<firstline>
– строка 1 Ага, это тесто – строка 3 Ха-ха-ха-ха-ха-хавыавыаывавыавыавыавыавыавыа – строка 4 фыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыы – строка 5 фыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыыфыф – строка 6 Пока
</firstline>
Как видно, этого объема данных достаточно, чтобы нарушать конфиденциальность и даже чтобы угонять акки с сервисов, которые шлют регистрационные данные на e-mail (например Facebook).
Мало-мало
В общем, все понятно – бага простейшая, но есть пара вопросов:
- Как получить доступ к НУЖНОМУ ящику?!
- Как получить НУЖНОЕ письмо в ящике?
Именно эти вопросы задал бы любой адекватный взломщик, которого интересуют более точечные и целевые атаки. Ведь мы имеем дело с ids, а знать ids вражеского письма нам не всегда дано (почти никогда). Таким образом, атака становится чуть более опасной и интересной с точки зрения техники (ids подменить почти каждый может, а вот вычислить его…)
И есть еще кое-какие детали, которые также были выяснены опытным путем. Оказывается, ids – это не просто идентификатор письма или папки, это фактически объект.
Первые 4 цифры – это номер ноды (я не в курсе, как это правильно называется, поэтому буду называть так), на которой
В середине идентификатор типа – папка или сообщение (у сообщения 00, у папки вроде 65), а последние цифры – идентификатор сообщения на ноде. Вот как раз только последнее менять и можно. То есть в моем примере текущее сообщение было с номером 2590081157. Теперь вы примерно представляете объем сообщений на одной ноде.
Второй забавный факт: идентификатор сообщения – абсолютное, инкрементируемое значение. То есть следующее письмо, обработанное (полученное) текущей нодой, будет 2590081158. Даже если это письмо будет направлено не мне. Вот теперь можно решить ОБЕ задачи. То есть выполнить целевую атаку, а не тупо сдампить миллиард писем 8)
Атака
Необходимо создать почтовый ящик на той же ноде, что и у жертвы. Задача трудно решается, если ящик жертвы зарегистрирован давно. Можно выкупить или угнать ящики с разных нод и зарегистрировать ящики на тех нодах, где это возможно. Но если ящик жертвы создан недавно, то атака имеет стопроцентную вероятность успеха. Так, например, будем атаковать ящик target-mail1@yandex.ru. Он у нас создан недавно. Поэтому успех атаки высок. Итак, создаем кучу ящиков: check-mail1, check-mail2, check-mail3 и т.д. Главное, чтобы все создаваемые ящики были на разных нодах. После чего отправляем письмо на target-mail1 и в копию ставим все созданные нами ящики.
В чем юмор? Когда мы увидим ids письма в наших аккаунтах “check-mail”, то попробуем сделать инкремент и декремент этого значения на 5 единиц. И на той ноде, где check-mailN совпал с target-mail1, мы увидим наше письмо. Смотрите пример:
Так мы можем получить доступ к целевому ящику. А что же с целевым письмом? А вот если вспомнить, то тот модуль, через который мы эксплуатируем уязвимость, показывает нам соседние письма в той же папке того же ящика (помните, что скрипт показывает три сообщения: то что указано в ids, и два соседних из того же ящика). Если выбранное письмо последнее, то покажут нам только два письма: текущее и предыдущее, включая его ids. Это значит, что мы можем выбрать этот ids для следующего запроса: тогда мы увидим его вторым объектом в XML-ответе, а третьим будет еще более старое письмо. Потом мы берем уже его ids и так «скользим» вниз, пока не прочтем все сохраненные в папке письма и не найдем искомое.
Выводы
Покрытие кода при тестах на проникновение и фаззинге – иногда ключевой параметр. Здесь мы увидели отличный пример, когда все пробовали менять ids для модуля “message-body”, видели, что запрос не прокатывает, и потому не стали делать аналогичные тесты с другими, менее очевидными модулями.
Хоть уязвимость и простая, сама атака достаточно сложная и требует анализа генерации ids и архитектуры нод. Эксплуатировать уязвимость интереснее, чем просто искать «дыру». Это был интересный челлендж по угону конкретного письма с конкретного ящика. Это сложно для старых ящиков, но все же можно, а для свежесозданных ящиков вероятность успеха 100%. Для старых –зависит от возможности завести почтовый ящик на той же ноде. Статистика по свежим регам: каждый третий или четвертый созданный ящик попадает на одну и ту же ноду. Период возможности регистрации на ноде с момента ее запуска – около полугода (то есть «новый»/«свежесозданный» ящик, это ящик созданный не далее чем пол года назад). По идее, потом, спустя полгода, зарегистрироваться на этой ноде уже, как я догадываюсь, нельзя -перегружена.
Благодарности:
Антону Карпову (tokza) и компании «Яндекс» – за интересный челлендж и конкурс, ну и за второе место. Ведь уязвимости есть везде, а такой конкурс помог закрыть многие из них!
Дмитрию Частухину (chipik) – за то, что не сильно расстроился, когда узнал о баге, которую «уже проверял»…
d0znpp, kyprizel, ptsecurity – за участие в конкурсе и за нахождение страшных дырок, что не позволяло мне расслабиться весь месяц (ненавижу конкурсы).
На последок «пруф» видео…
Всем пока!
PS: Так как я не сотрудник «Яндекса», детали архитектуры, алгоритмы и названия могут быть недостоверными. Все выводы об устройстве «Яндекс.Почты» основаны на внутренних ощущениях и интуитивном видении автора.
Автор: d00kie