Table of Contents
Для понимания обнаруженных уязвимостей понадобятся хотя бы базовые знания о том, что такое CSRF. Если их нет — не расстраивайтесь, ниже я попробовал доступно это объснить (правда, по-моему, не получилось). Если вы и так в курсе, что это такое, или же наоборот, не собираетесь в этом разбираться — смело переходите к результатам моего поверхностного аудита.
Для начала имеет смысл почитать Википедию. Если читать по-английски лень (на русском языке в Википедии почти ничего по теме нет, на Хабре тема освещена чрезвычайно слабо), а узнать что-нибудь хочется, то вот краткое содержание предыдущих серийописание этой уязвимости.
UPD: главный разработчие Liveinternet.ru не верит в существование CSRF. Раскрываю его уязвимости раньше обещанной недели.Здесь пример использования. Будьте внимательны, если у вас есть аккаунт в LiRu.
UPD 2: Работа уязвимости продемонстрирована, пример пока убрал.
UPD 3: Неделя прошла. Выкладываю работающие/работавшие примеры. Времени мало, поэтому пока не проверяю, что работает, а что нет. Буду признателен, если отпишете об этом в комментариях. Все вопросы, пожелания, предложения — в ответах к этому комментарию.
Рамблер, Mail.Ru, Liveinternet, Яндекс
Что такое CSRF?
Для выполнения каких-либо действий с Web приложением пользователи отправляют HTTP запросы. Для того, чтобы определить, от имени какого пользователя выполняется действие, обычно используются сессии, данные о которых хранятся в Cookie. Сервер получает запрос, смотрит Cookie, убеждается в том, что сессия связанная с данными Cookie существует и выполняет действие от имени пользователя, связанного с этой сессией. Для того, чтобы пользователь мог оставаться залогиненым на своем любимом ресурсе, браузеры автоматически отправляют Cookie тому серверу, который их установил. Таким образом, если из браузера пользователя отправить запрос, то вместе с ним отправятся и Cookie. И сервер будет думать, что имеет дело с залогиненым пользователем ресурса и действия выполнит от его имени. А заставить браузер отправить запрос можно и со сторонней страницы. И если сервер такие запросы будет обрабатывать так же, как и запросы со своих страниц — это и есть уязвимость.
Как воспользоваться?
Немного запутано, конечно, получилось, поэтому простой пример. Можно из всего объяснения читать только его.
Чтобы удалить свой профиль из популярной социальной сети Васе нужно кликнуть по ссылке «Удалить профиль», которая ведет на страницу http://vulnerable.site/delete_profile.php?sure=yes. Петя размещает на своей странице картинку, которая должна загружаться по этому адресу (понятное дело, что никакой картинки по этому адресу нет, но браузер заранее этого не знает). Когда Вася заходит на страницу Пети, браузер Васи пытается загрузить эту картинку. Картинки он не находит и успокаивается. Но популярная социальная сеть получила от имени Васи команду удалить профиль. И профиль Васи был благополучно удален. Возможно вам все равно непонятно — поменяйте Васю и Петю на Alice и Bob и прочитайте еще раз. От этих Васи с Петей всегда сплошная путаница.
Итак, для использования придется заманить пользователя на собственноручно созданную страницу (или же на страницу с XSS, на которой через XSS размещен ваш код — но для данной статьи разницы нет). На этой странице должно быть что-то, что отправит запрос. Самый простой способ — разместить картинку:
<img src="https://vulnerable.site/delete_profile.php?sure=yes"/>
Можно взломать популярный блог и разместить там скрытый iframe — результат будет потрясающим.
А в моём приложении все действия выполняются через POST запросы
Почему-то многие разработчики уверены, что невозможно воспользоваться CSRF уязвимостью, требующей отправки POST запроса. Обычно они мотивируют это тем, что через JS нельзя отправить POST запрос на другой домен. Это действительно так (по крайней мере при обычных условиях). Но почему-то всегда забывают о корне мирового зла теге iframe. Во-первых, он может быть скрытым (display:none) и при этом всё равно загрузится. Во-вторых, загрузить его можно откуда угодно (со своего сервера в том числе). И, в-третьих, в нем может быть уже заполненная форма с method=«POST» и action=«http://vulnerable.site/delete_profile.php», а по onload вызываться submit этой формы. Для демонстрации нижеперечисленных уязвимостей я использовал именно такой метод.
Я пользователь — как мне защититься?
Тут я должен вас огорчить. Единственный способ защиты — не пользоваться интернетом. Или хотя бы той его частью, в которой нужно куда-нибудь логиниться (социальные сети, твиттер, электронная почта, форумы и т.д.). Нет, можно конечно никогда не оставлять открытые сессии и всегда нажимать кнопку выход сразу после использования ресурса — но это все равно вас не спасет, т.к. пока вы залогинены — вы уязвимы. Обычным пользователям остается только надеятся на профессионализм разработчиков и на то, что они позаботились о своих пользователях. А надежды мало.
UPD: Для FF есть addon — RequestPolicy, спасибо Vanav
Я разработчик — как мне позаботиться о своих пользователях?
Именно позаботиться о своих пользователях — использование CSRF сервера из строя не выводит и SSH доступ не дает. Всего-лишь какие-то действия от имени пользователя выполняются. Ну так пользователь и сам мог их выполнить. Поэтому некоторые разработчики это и уязвимостью не считают. А я считаю такое отношение вопиющим непрофессионализмом и наплевательством на собственных пользователей. Вон из профессии. Тем более, что уязвимость далеко не новая.
Подробно останавливаться на способах защиты я, пожалуй, не буду — просто перечислю способы и дам небольшие комментарии — т.к. если вы разработчик, то и сами можете уже должны в этом разбираться.
Сразу скажу, что способы взял из английской Википедии и, по моему личному мнению, многие из них никакой защитой не являются.
- Одноразовый токен для каждого действия — самый надёжный способ, лично я выбираю его
- В каждом запросе требовать передачи логина и пароля — тоже надежно, но если используется не HTTPS, то пароль можно украсть из любого запроса с помощью сниффера
- Ограничение времени жизни сессии — ни от чего не защищает, просто сокращает время, в течение которого можно воспользоваться уязвимостью
- Проверка заголовка Referer — неплохо, но этот заголовок устанавливается не всегда, первый способ всё же предпочтительнее
- Убедиться, что clientaccesspolicy.xml и crossdomain.xml (для Silverlight и Flash соответственно) не позволяют запросы из непроверенных источников — это как сначала снять замок с двери и разрешить ходить кому угодно, а потом для защиты повесить его обратно
- Проверять наличие X-Requested-With — поможет для AJAX запросов да и то не всегда
Практика
В принципе, поиск CSRF — очень простое занятие. Просто заводим аккаунт на интересующем нас ресурсе, включаем Firebug/Chrome Developer Tools и делаем всё то, что делает обычный пользователь — настраиваем почтовый ящик, читаем, пишем, удаляем письма, пишем в блоги и т.д. При этом не забываем смотреть на отправленные запросы. Если запрос не содержит непонятно как сформированных данных (различные одноразовые токены) — это потенциальная уязвимость. Повторяем этот запрос с локальной страницы и смотрим на результат. Если действие произошло — уязвимость обнаружена. Пишем страничку с формой, загружаем на свой сервер и грузим эту страницу в скрытом iframe с какой-нибудь другой своей страницы — просто для демонстрации работы (или для промышленного использования). Можно ещё каким-нибудь своим знакомым показать и на них проверить. Только трезво оценивайте уязвимость и свои отношения со знакомыми — вряд ли одноклассник Петя обрадуется, если с его счета будет переведено 300 тысяч долларов.
Некоторые запросы не получилось отследить ни через Firebug, ни через Chrome Developer Tools — сначала отправляется AJAX запрос, а после получения ответа на него с помощью JS открывается следующая страница. В этом случае интересующий нас запрос видно только несколько секунд. Но от Wireshark все равно никому не скрыться.
UPD: Умные люди в комментариях подсказывают, что в FireBug есть кнопка «Не очищать» а в Chrome Developer Tools — «Preserve Log upon Navigation»
При поиске уязвимостей нельзя забывать о дополнительных сервисах (вроде календарей Яндекс) и мобильных версиях.
Список уязвимостей
Я не ставил перед собой задачу найти все CSRF узявимости на определенном портале. На каждый ресурс выделялось не больше 2х часов. Проверял только русские порталы — Google.ru, Facebook.com и им подобные остались в стороне. На некоторых порталах за отведенное время я не обнаружил ничего, но в список внес, чтобы оставить общий отзыв об их способе защиты. Некоторые порталы из списка я не считаю крупнейшими в Рунете, но так считает рейтинг, которым я пользовался.
Liveinternet.ru
Защиты от CSRF нет вообще никакой. Сделать можно всё, что угодно — отправлять личные сообщения, постить в блоги, писать комментарии, перенастраивать аккаунт, в общем всё, что вообще позволяет ресурс. Поэтому составлять список уязвимостей не стал. Этот ресурс был последним, до которого я добрался, да и не считаю я его одним из крупнейших. После него у меня уже руки опустились — все стало и так понятно.
Rambler.ru
Защита от CSRF по методу №1 — одноразовый токен. Казалось бы никаких CSRF быть не может. Но, во-первых, эта защита есть не везде. А, во-вторых, токен не проверяется — можно отправить любой токен (и даже пустой) и все равно нужно действие выполнится. В качестве дополнительной защиты в одной из форм увидел скрытое поле «referer» с адресом текущей страницы. Даже прослезился немного.
Создается впечатление, что разработчикам Рамблера в один прекрасный день сказали, что есть такая уязвимость CSRF и что для защиты от нее нужно с каждым запросом отправлять уникальный токен. Но забыли сказать, что его еще и проверять надо.
Что можно сделать
- В Рамблер.Друзьях (да кто ими вообще пользуется?) можно создать группу друзей с произвольным названием, добавить или удалить произвольного друга, изменить настройки уведомлений о действиях друзей/добавлении в друзья
- В настройках аккаунта можно добавить или удалить пользователя из черного списка, изменить подпись, изменить местоположение, изменить список своих интересов, разрешить/запретить отображать список своих друзей и своё ФИО
- В почте можно отправить произвольное письмо от имени пользователя, включить безусловную переадресацию на произвольный e-mail (без сохранения), изменить настройки почты (особенно ценно включить отображение изображений со сторонних серверов), удалить все письма из папки (на самом деле они переместятся в корзину) и очистить корзину
- Также в почте можно удалить письмо, пометить письмо как прочитанное и переслать письмо на произвольный почтовый ящик. Правда для этого нужно знать id письма и имя папки, в которой оно хранится. 2 папки известны — «Входящие» и «Отправленные». id письма — номер письма в текущей папке, так что брутфорс никто не отменял
В общем, огромный простор для спама и махинаций с почтой. Даже потенциальные сценарии использования обдумывать не стал — проще сказать, что нельзя сделать без ведома пользователя.
Это далеко не весь перечень, т.к. защиты нет (вернее она есть, но не работает, что равносильно).
Mail.ru
На почте защита от CSRF по методу №2 — ввод пароля. Т.е. изначально это не от CSRF защита, но там, где требуется ввод пароля, CSRF тоже не работает. На других сервисах есть и нормальная защита.
Итак, какие узявимости найдены в популярнейшем почтовом сервисе СНГ
- Отправка от имени пользователя письма с любым текстом и темой на любой почтовый ящик. Письмо с уникальным кодом может быть отправлено на почтовый ящик злоумышленника и тогда он узнает email посетителя. Этот email через AJAX попадет на страницу, на которой пользователь уже находится — это пригодится для использования уязвимостей №2, №7
- Отправка копии письма на любой адрес (письмо не сохраняется в отправленных, пользователь об этом никак не может узнать). Для использования этой уязвимости нужно было знать id письма — 20 цифр, из которых первые 10 — timestamp, а вторые 10 — номер письма среди обработанных сервером в эту секунду (это только мое личное предположение конечно же). По моим наблюдениям это число не превышает 1000. Можно попросить сервер отправить сразу 1000 писем и тогда он отправит те из них, которые действительно есть у пользователя в почтовом ящике, хотя в интерфейсе pro.mail.ru (а именно там находится эта уязвимость) сказано, что переслать больше 3х писем одновременно нельзя. Использовать можно брутфорсом — переслать письма полученные за 10 секунд, запрашивая пересылку каждый раз по 1000 штук. Чтобы пробрутфорсить письма за день надо 2.5Гб исходящего трафика, но для кражи письма с паролем это и не нужно — на почтовый ящик пользователя (он уже может быть известен из №1) запускается восстановление пароля на стороннем ресурсе и время прихода письма уже приблизительно известно. Можно использовать вместе с уязвимостью №3
- Удаление письма — опять же по id. Опять же брутфорсом. Запускаем восстановление пароля, крадем письмо с паролем и удаляем его
- Подключение и отключение СМС уведомлений — тут вообще всё просто. Некоторые настройки почтового ящика можно изменить не вводя пароль. Можно отключить уведомления, а можно включить их на свой телефон, привязанный в пункте №7
- Отключить сохранение отправленных писем и включить Web mail-агент на страницах почты — эти галочки находятся на одной странице в настройках и так же (как и предыдущая уязвимость) не требовали пароля для изменения состояния. Отключать сохранение отправленных понятно зачем. А включить Web mail-агент имеет смысл, если в нем тоже есть CSRF
- Отключить уведомления о приближении лимита размера почтового ящика — суть та же, что и в 2х предыдущих. Отключаем уведомления и заваливаем ящик большими письмами
- Привязать/удалить номер телефона — с этим всё немного интереснее. Для всех действий требуется email пользователя (вернее username и domain) которые можно получить из пункта №1. Также требуется указывать номер телефона, так что удалить у пользователя его собственный номер телефона не получится (по крайней мере если его не узнать заранее — например, он отображается авторизованным контактам в mail агенте — а как авторизоваться, это уже другой вопрос). Чтобы привязать свой номер телефона надо отправить соответствующий запрос. После этого на телефон придет СМС с кодом. На этом этапе понадобится софт, который будет код из СМС доставать и по AJAX запросу его сообщать. JS же на страничке получив этот код создаст еще один iframe, который выполнит подтверждение привязки номера телефона. Удалить этот номер телефона можно таким же образом. Тут правда есть одна особенность — если номер телефона единственный, то удалится он только через 15 дней, а не сразу. Удалить его сразу можно только зная код из СМС (он тот же самый, что и для привязки)
Yandex
Защита от CSRF есть. Но т.к. Яндекс — это не только поиск или почта, то и защита везде разная. А где-то что-то пропущено. Яндекс в течение 3х дней ответили на моё письмо и обещали проверить информацию. И они единственные, кто исправил хотя бы одну уязвимость.
Что же можно (было) сделать
- Можно было перенастроить поиск — включить сохранение поисковых запросов и результатов в сервисе «Находки», изменить уровень фильтрации поиска (например, запретить искать порнографию коллеге по работе — просто так, из вредности). Эта уявимость на данный момент закрыта
- Можно изменить местоположение пользователя — и будут ему показываться результаты поиска, реклама и пробки в Херсоне вместо Питера. А можно изменить всем посетителям местоположение на какое-нибудь не очень популярное и гнать туда рекламу из Директа по ценам значительно ниже московских
- Можно создать в календаре пользователя ToDo-list с произвольным именем и добавить в него произвольные дела. Для добавления дел нужно знать id списка, но можно ведь сначала создать список у себя, затем у пользователя, а потом снова у себя и перебрать id между двумя своими списками (а, с учетом количества создаваемых списков, скорее всего даже перебирать ничего не придется). Уязвимость в мобильной версии Яндекс.Календарь
- Можно из Яндекс.Паспорта удалить телефон. Нужно знать его id, но зная примерную дату добавления можно его и брутфорсом подобрать
- В Яндекс.Маркете можно добавить произвольный товар в список сравнения. Или удалить его оттуда.
- В Яндекс.Маркете можно подписать пользователя на уведомление о снижении цены на конкретный товар ниже определенного уровня. Или отписать пользователя от уведомлений. Например, недобросовестный интернет-магазин закупил партию серых iPhone и планирует акцию по их распродаже. Перед самой акцией он устраивает какую-нибудь PR-кампанию и всех посетителей подписывает на уведомление о снижении цены ниже 8000 рублей. А затем в Яндекс.Маркет экспортируется iPhone по цене 7999 рублей и все пользователи получают от Яндекса спам с предложением купить этот самый iPhone
- Глобальный логаут. Можно завершить все сессии пользователя на всех компьютерах. Можно пользоваться как мелкой пакостью. Конкуренты могут на своих сайтах размещать iframe, который будет завершать все сессии Яндекса, пользователи будут раздражаться. Вообще, единственный сайт на котором такой проблемы нет — Вконтакте. Там с неправильным токеном даже из аккаунта не выйдешь. Не уверен, можно ли считать это уязвимостью вообще, но похоже Вконтакте считает
Не самые серьезные уязвимости. Скорее всего причина их появления в том, что Яндекс — это большое количество сервисов, каждый со своей командой разработчиков и везде защита организована по-разному. А где-то просто недосмотрели.
А как же Вконтакте и ЖЖ?
Конечно же эти ресурсы я не мог обойти своим вниманием. Но уязвимостей не нашел, хотя это и не значит, что их там нет. В любом случае стоит рассказать, как у них устроена защита от CSRF.
Вконтакте
Токены на любые действия пользователя. Похоже добавляются автоматически, поэтому о них не забывают. Точно такая же защита и в мобильной версии. Один сервис, одна команда разработчиков, нет таких проблем, как, например, у Яндекса. Смущает меня только одно — токен для конкретного действия у конкретного пользователя всегда один и тот же. Т.е. это некая смесь способов защиты №1 и №2 — токены не одноразовые, но известны только пользователю. Можно считать их паролем для выполнения действия — на каждое действие свой пароль. Что-то мне в такой организации не нравится, но я пока не понял, что именно.
ЖЖ
Защита по методу №1 — одноразовые токены. В мобильной версии защита немного проще, но все же есть. Вот на кого можно смотреть в качестве примера. Стоит правда заметить, что сервис-то не очень и русский. Так что он вне конкурса.
Одноклассники
Очень хотел проверить и их. Но для этого нужно уметь ими пользоваться. А у меня от их дизайна нервный тик. Так что не сложилось. Попробую как-нибудь ночью с отключенными картинками это исправить.
Вывод
В общем, картина совершенно нерадостная. Если такие монстры допускают такие ошибки в своих проектах, то всем остальным разработчикам стоит еще раз проверить свои ресурсы. Первое место по скорости закрытия уязвимостей занимает Яндекс со скоростью 1 уязвимость в полтора месяца. Остальные ресурсы занимают почетное второе место.