# Атака CSRF Нельзя говорить про AJAX и не упомянуть про важнейшую деталь его реализации -- защиту от CSRF-атак. [CSRF](http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D0%B4%D0%B5%D0%BB%D0%BA%D0%B0_%D0%BC%D0%B5%D0%B6%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2%D1%8B%D1%85_%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2) (Cross-Site Request Forgery, также XSRF) -- опаснейшая атака, которая приводит к тому, что хакер может выполнить на неподготовленном сайте массу различных действий от имени других, зарегистрированных посетителей. Какие это действия -- отправка ли сообщений, перевод денег со счёта на счёт или смена паролей -- зависят от сайта, но в любом случае эта атака входит в образовательный минимум веб-разработчика. [cut] ## Злая форма "Классический" сценарий атаки таков: Итог атаки -- Вася, зайдя на злую страницу, ненароком отправил письмо от своего имени. Содержимое письма сформировано хакером. ## Защита В примере выше атака использовала слабое звено авторизации. **Куки позволяют сайту `mail.com` проверить, что пришёл именно Вася, но ничего не говорят про данные, которые он отправляет.** Иначе говоря, куки не гарантируют, что форму создал именно Вася. Они только удостоверяют личность, но не данные. Типичный способ защиты сайтов -- это "секретный ключ" (`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`, будет выглядеть так: ```html
*!* */!*
``` При её отправке сервер проверит поле `csrf`, удостоверится в правильности токена, и лишь после этого отошлёт сообщение. "Злая страница" при всём желании не сможет сгенерировать подобную форму, так как не владеет `secret`, и токен будет неверным. Такой токен также называют "подписью" формы, которая удостоверяет, что форма сгенерирована именно на сервере. [smart header="Подпись с полями формы"] Эта подпись говорит о том, что автор формы -- сервер, но ничего не гарантирует относительно её содержания. Есть ситуации, когда мы хотим быть уверены, что некоторые из полей формы посетитель не изменил самовольно. Тогда мы можем включить в MD5 для формулы токена эти поля, например: ``` token = salt + ":" + MD5(salt + ":" + secret + ":" + fields.money) ``` При отправке формы сервер проверит подпись, подставив в неё известный ему `secret` и присланное значение `fields.money`. При несовпадении либо `secret` не тот (хакер), либо `fields.money` изменено. [/smart] ## Токен и AJAX Теперь перейдём к AJAX-запросам. Что если посылка сообщений в нашем интерфейсе реализуется через XMLHttpRequest? Как и в случае с формой, мы должны "подписать" запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не "злой страницей". Здесь возможны варианты, самый простой -- это дополнительная кука.
  1. При авторизации сервер устанавливает куку с именем `CSRF-TOKEN`, и пишет в неё токен.
  2. Код, осуществляющий XMLHttpRequest, получает куку и ставит заголовок `X-CSRF-TOKEN` с ней: ```js var request = new XMLHttpRequest(); var csrfCookie = document.cookie.match(/CSRF-TOKEN=([\w-]+)/); if (csrfCookie) { request.setRequestHeader("X-CSRF-TOKEN", csrfCookie[1]); } ```
  3. Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.
Защита действует потому, что прочитать куку может только JavaScript с того же домена. "Злая страница" не сможет "переложить" куку в заголовок. Если нужно сделать не XMLHttpRequest, а, к примеру, динамически сгенерировать форму из JavaScript -- она также подписывается аналогичным образом, скрытое поле или дополнительный URL-параметр генерируется по куке. ## Итого