8 марта 2013 г.

Защита от CSRF в ASP.NET MVC

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

Суть CSRF атаки заключается в том, что какое-то приложение или сторонний ресурс отправляют от вашего имени запросы на сервер.
Представьте себе ситуацию, когда вы авторизовались под собой на каком-то сайте, например, на сайте интернет-магазина, в котором зарегистрированы. Далее вы спокойно продолжаете заниматься обычными, хорошими делами, и никого не трогаете. А в один прекрасный момент обнаруживаете, что в том интернет-магазине кто-то оставил отзыв от вашего имени. Как же так получилось, ведь пароль известен только вам? Все просто. Произошла CSRF-атака.

Сегодня мы попробуем имитировать простую CSRF-атаку, а также реализуем механизм защиты  от нее.
Подделка межсайтовых запросов использует механизм работы HTTP-протокола. Дело в том, что когда пользователь авторизовался на сайте, в cookies сохраняются данные, что текущий пользователь авторизован, и вход повторять не нужно. Как мы знаем, cookies хранятся и на клиенте (в браузере), и на сервере. Вот как это происходит... Пока браузер хранит cookies об успешной авторизации на сайте, то все запросы на этот сайт от имени текущего браузера, будут считаться правомерными и легальными. В этом и вся проблема. Получается, что любой другой сайт, открытый в этом же браузере, или любое приложение, использующее этот браузер по-умолчанию, может подделать/имитировать запрос на сайт так, как будто его совершили вы.

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

Давайте разберемся с этим на живом примере.
Я создал простое MVC приложение, которое содержит в себе подключенную БД с настроенным Membership для авторизации. Также, в БД есть таблица Persons. Работа приложения заключается в реализации Create/Read/Delete логики над этой таблицей. Мы можем просматривать список всех персон, добавлять новую или удалять старую. Важным моментом есть то, что просматривать список пользователей может кто-угодно, а для добавления/редактирования необходимо пройти авторизацию на сайте.

При запуске приложения, мы видим страницу списка существующих персон. Сейчас он пуст:

После перехода по ссылке "Create New", нам необходимо будет авторизоваться т.к. добавлять персон может только авторизовавшийся пользователь:

При успешном входе, мы попадем на страницу добавления:

После добавления, нас вернут на список все персон, где уже должно быть что-то новое:

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

Теперь представьте, что вам кто-то скинул странный файлик, и попросил запустить его:

Антивирус на этот файл не ругается, и доверчивый пользователь запустит его, не поинтересовавшись содержимым. Напомню, что в это время, вы авторизованы под собой на нашем сайте. А сайт ведь в браузере, а программа - на рабочем столе. Чего бояться то?
Так вот, при запуске этого странного файла на выполнение, пользователь увидит следующее:

Обратите внимание на то, что в списке появилась новая персона, которую добавили не вы! Вот она - типичная CSRF-атака.

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

   1:  <html>
   2:      <head>
   3:          <title>Hello CSRF</title>
   4:      </head>
   5:      <body>
   6:          <h1>Ha-ha-ha!</h1>
   7:          <form action="http://localhost:4283/Home/Create"
                                                  method="post" id="fakeForm">
   8:              <div style="display: none;">
   9:                <input type="text" id="Name" name="Name" value="Bad Man" />
  10:                <textarea id="Notes" name="Notes">Bla bla bla bla</textarea>
  11:                <input type="submit" value="Save" />
  12:              </div>
  13:          </form>
  14:          <script type="text/javascript">
  15:              document.getElementById('fakeForm').submit();
  16:          </script>
  17:      </body>
  18:  </html>

Оказало, что это обычная html-страница, расширение которой было изменено на hta для неузнаваемости. Внутри страницы помещена скрытая форма, содержащая все поля, идентичные полям этой же страницы на вашем сайте. Также указан адрес для отправки формы - адрес на нашем сервере. Все эти данные (набор необходимых полей и адрес отправки запроса) злоумышленник обычно узнает заранее. При открытии этого файла отрабатывает javascript, который просто принудительно отправляет форму. Таким образом, по указанному адресу уходят параметры для добавления новой персоны, и делается это через браузер, в котором мы авторизовались ранее. Поэтому сервер думает, что запрос настоящий, и добавляет новую персону в БД.


Рассмотрим варианты противостояния CSRF.

Самый популярный выход - использование вместо HTTP защищенного HTTPS протокола. При этом уже никто не сможет ничего подделать. Но на внедрение HTTPS нужно потратить большое количество ресурсов: купить сертификат, настроить IIS, подогнать приложение под работу с этим протоколом.

К счастью, инфраструктура ASP.NET MVC предоставила нам отличный, простой способ защиты от CSRF-атак при отправке форы на нашем сайте.

Для реализации этой защиты нам необходимо выполнить два шага:
1) добавить на форму специальный хелпер AntiForgeryToken:
2) добавить для метода Create контроллера фильтр ValidateAntiForgeryToken:
AntiForgeryToken добавляет в форму зашифрованный ключ. Этот ключ знает только наше приложение. При отправке запроса, метод контроллера проверяет, совпадает ли принятый ключ с тем, который определило приложение. Если да, то такой запрос считается правомерным.
Вот как выглядит этот ключ на форме:

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


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

Конечно же, если есть возможность, то лучше всего использовать HTTPS для защиты. Но в таких простых ситуациях сойдет и AntiForgeryToken. А простота заключается в том, что этот механизм защиты применим только к формам и их отправке.
В данном примере мы подделывали запрос из отдельного файлика. В реальности, имитации запросов могут происходить даже с сайта, открытого в соседней вкладке вашего браузера.

На этом все. Надеюсь, что кому-то будет полезно.

_____
Исходники, БД и зловред