# Атака 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`. У него есть сессия в куках.
- Вася попал на "злую страницу", например хакер пригласил его сделать это письмом или как-то иначе.
- На злой странице находится форма такого вида:
```html
```
- При заходе на злую страницу JavaScript вызывает `form.submit`, отправляя таким образом форму на `mail.com`.
- Сайт `mail.com` проверяет куки, видит, что посетитель авторизован и обрабатывает форму. В данном форма предполагает посылку сообщения.
Итог атаки -- Вася, зайдя на злую страницу, ненароком отправил письмо от своего имени. Содержимое письма сформировано хакером.
## Защита
В примере выше атака использовала слабое звено авторизации.
**Куки позволяют сайту `mail.com` проверить, что пришёл именно Вася, но ничего не говорят про данные, которые он отправляет.**
Иначе говоря, куки не гарантируют, что форму создал именно Вася. Они только удостоверяют личность, но не данные.
Типичный способ защиты сайтов -- это "секретный ключ" (`secret`), специальное значение, которое генерируется случайным образом и сохраняется в сессии посетителя. Его знает только сервер, посетителю мы его даже не будем показывать.
Затем на основе ключа генерируется "токен" (`token`). Токен делается так, чтобы с одной стороны он был отличен от ключа, в частности, может быть много токенов для одного ключа, с другой -- чтобы было легко проверить по токену, сгенерирован ли он на основе данного ключа или нет.
Для каждого токена нужно дополнительное случайное значение, которое называют "соль" `salt`.
Формула вычисления токена:
```
token = salt + ":" + MD5(salt + ":" + secret)
```
Например:
- В сессии хранится `secret="abcdef"`, это значение создаётся один раз.
- Для нового токена сгенерируем `salt`, например пусть `salt="1234"`.
- `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?
Как и в случае с формой, мы должны "подписать" запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не "злой страницей".
Здесь возможны варианты, самый простой -- это дополнительная кука.
- При авторизации сервер устанавливает куку с именем `CSRF-TOKEN`, и пишет в неё токен.
- Код, осуществляющий 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]);
}
```
- Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.
Защита действует потому, что прочитать куку может только JavaScript с того же домена. "Злая страница" не сможет "переложить" куку в заголовок.
Если нужно сделать не XMLHttpRequest, а, к примеру, динамически сгенерировать форму из JavaScript -- она также подписывается аналогичным образом, скрытое поле или дополнительный URL-параметр генерируется по куке.
## Итого
- CSRF-атака -- это когда "злая страница" отправляет форму или запрос на сайт, где посетитель, предположительно, залогинен.
Если сайт проверяет только куки, то он такую форму принимает. А делать это не следует, так как её сгенерировал злой хакер.
- Для защиты от атаки формы, которые генерирует `mail.com`, подписываются специальным токеном. Можно не все формы, а только те, которые осуществляют действия от имени посетителя, то есть могут служить объектом атаки.
- Для подписи XMLHttpRequest токен дополнительно записывается в куку. Тогда JavaScript с домена `mail.com` сможет прочитать её и добавить в заголовок, а сервер -- проверить, что заголовок есть и содержит корректный токен.
- Динамически сгенерированные формы подписываются аналогично: токен из куки добавляется как URL-параметр или дополнительное поле.