CSRF (Сross Site Request Forgery)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 14:03, 9 июня 2016.

CSRF (англ. Cross Site Request Forgery - Межсайтовая подделка запросов) - атака, которая приводит к тому, что злоумышленник может выполнить в веб-приложении действие от имени авторизованного пользователя. Данная атака использует недостатки протокола HTTP. Впервые теоретические рассуждения по поводу возможности такой атаки появились в 1988 году, а первые уязвимости были обнаружены в 2000 году. Сам термин ввел Peter Watkins в 2001 году. Основные применение CSRF - выполнение действий от лица пользователя, CSRF является client-side уязвимостью.

Пример

Рассмотрим пример базовой CSRF уязвимости, чтобы понять ее принцип. В качестве примера, возьмем Low Level CSRF в веб-приложении DVWA. Откроем страницу. Используем кольцевой адрес в качестве proxy, запустим Burp, который будет использовать кольцевой адрес для перехвата и модификации запросов. Посмотрим, что же происходит.
Для начала откроем страницу и посмотрим код.
CSRF1.png
Результат следующий:

  1. Имеем GET форму, в которой есть 3 элемента: password_new, password_conf, Change.
  2. При отправке запроса передается PHPSESSID, который отвечает за сессию приложения.

Попробуем поменять пароль:
CSRF2.png

  1. Введенные параметры передаются в URL в открытой форме.
  2. При работе с формой нет никаких дополнительных cookies.

Проведем самую простую CSRF атаку.
Отправим цели следующую ссылку: http://192.168.56.101/crackit/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change
При переходе по этой ссылке, пользователь, имеющий сессию на сервере поменяет свой пароль на hacked.
Другой способ: можем поднять сервер, после чего создать там следующую html страницу:

<html>
<head>
<title> PHISHED </title>
</head>
<body>
<img src = "http://192.168.56.101/crackit/dvwa/vulnerabilities/csrf/?password_new=hacked&password_conf=hacked&Change=Change"/>
</body>
</html>


Перейдя на такую страницу, браузер пользователя попытается подгрузить картинку, а значит перейдет по ссылке, после чего пароль поменяется на hacked.
В обоих случаях итог следующий:
CSRF3.png
Это и был пример простейшей CSRF атаки.

Защита от CSRF

Атака выше использовала слабое звено авторизации. PHPSESSID удостоверял личность пользователя, но не данные, которые он отправлял.
Наиболее популярный способ защиты сайтов - "секретный ключ" (secret), специальное значение, которое генерируется случайным образом и сохраняется в сессии посетителя. Его знает только сервер, посетителю мы его даже не будем показывать. Затем на основе ключа генерируется "токен" (token). токен делается так, чтобы с одной стороны он был отличен от ключа, в частности, может быть много токенов для одного ключа, с другой - чтобы было легко проверить по токену, сгенерирован ли он на основе данного ключа или нет. Для каждого токена нужно дополнительное случайное значение, которое называют "соль" salt. Например, формула вычисления токена:
token = salt + ":" + MD5(salt + ":" + secret)
Далее

  1. В сессии хранится secret="abcdef", это значение создаётся один раз.
  2. Для нового токена сгенерируем salt, например пусть salt="1234".
  3. token = "1234" + ":" + MD5("1234" + ":" + "abcdef") = "1234:5ad02792a3285252e524ccadeeda3401".


Это значение – с одной стороны, случайное, с другой – имея такой token, мы можем взять его первую часть 1234 в качестве salt и, зная secret, проверить по формуле, верно ли он вычислен. Не зная secret, невозможно сгенерировать token, который сервер воспримет как правильный. Далее, токен добавляется в качестве скрытого поля к каждой форме, генерируемой на сервере. То есть, «честная» форма для отсылки сообщений, созданная на http://mail.com, будет выглядеть так:

<form action="http://mail.com/send" method="POST">
  <input type="hidden" name="csrf" value="1234:5ad02792a3285252e524ccadeeda3401">
  <textarea name="message">
    ...
  </textarea>
</form>

При её отправке сервер проверит поле csrf, удостоверится в правильности токена, и лишь после этого отошлёт сообщение. «Злая страница» при всём желании не сможет сгенерировать подобную форму, так как не владеет secret, и токен будет неверным. Такой токен также называют «подписью» формы, которая удостоверяет, что форма сгенерирована именно на сервере.

Подпись с полями формы

Подпись, представленная выше, говорит о том, что автор формы - сервер, но ничего не гарантирует относительно ее содержания.
Есть ситуации, когда мы хотим быть уверены, что некоторые из полей формы посетитель не изменил самовольно. Тогда мы можем включить в MD5 для формулы токена эти поля, например:
token = salt + ":" + MD5(salt + ":" + secret + ":" + fields.money)
При отправке формы сервер проверит подпись, подставив в неё известный ему secret и присланное значение fields.money. При несовпадении либо secret не тот (хакер), либо fields.money изменено.

Использование AJAX

Теперь перейдём к AJAX-запросам.
Что если посылка сообщений в нашем интерфейсе реализуется через XMLHttpRequest?
Как и в случае с формой, мы должны «подписать» запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не «злой страницей».
Здесь возможны варианты, самый простой – это дополнительная кука.

  • При авторизации сервер устанавливает куку с именем CSRF-TOKEN, и пишет в неё токен.
  • Код, осуществляющий XMLHttpRequest, получает куку и ставит заголовок X-CSRF-TOKEN с ней:
var request = new XMLHttpRequest();

var csrfCookie = document.cookie.match(/CSRF-TOKEN=([\w-]+)/);
if (csrfCookie) {
  request.setRequestHeader("X-CSRF-TOKEN", csrfCookie[1]);
}
  • Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.


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


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

Ссылки