140 lines
10 KiB
Markdown
140 lines
10 KiB
Markdown
# Атака 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]
|
||
|
||
## Злая форма
|
||
|
||
"Классический" сценарий атаки таков:
|
||
|
||
<ul>
|
||
<li>Вася является залогиненным на сайт, допустим, `mail.com`. У него есть сессия в куках.</li>
|
||
<li>Вася попал на "злую страницу", например хакер пригласил его сделать это письмом или как-то иначе.</li>
|
||
<li>На злой странице находится форма такого вида:
|
||
|
||
```html
|
||
<form action="http://mail.com/send" method="POST">
|
||
<input type="hidden" name="message" value="Сообщение">
|
||
...
|
||
</form>
|
||
```
|
||
</li>
|
||
<li>При заходе на злую страницу JavaScript вызывает `form.submit`, отправляя таким образом форму на `mail.com`.</li>
|
||
<li>Сайт `mail.com` проверяет куки, видит, что посетитель авторизован и обрабатывает форму. В данном форма предполагает посылку сообщения.</li>
|
||
</ul>
|
||
|
||
Итог атаки -- Вася, зайдя на злую страницу, ненароком отправил письмо от своего имени. Содержимое письма сформировано хакером.
|
||
|
||
## Защита
|
||
|
||
В примере выше атака использовала слабое звено авторизации.
|
||
|
||
**Куки позволяют сайту `mail.com` проверить, что пришёл именно Вася, но ничего не говорят про данные, которые он отправляет.**
|
||
|
||
Иначе говоря, куки не гарантируют, что форму создал именно Вася. Они только удостоверяют личность, но не данные.
|
||
|
||
Типичный способ защиты сайтов -- это "секретный ключ" (`secret`), специальное значение, которое генерируется случайным образом и сохраняется в сессии посетителя. Его знает только сервер, посетителю мы его даже не будем показывать.
|
||
|
||
Затем на основе ключа генерируется "токен" (`token`). Токен делается так, чтобы с одной стороны он был отличен от ключа, в частности, может быть много токенов для одного ключа, с другой -- чтобы было легко проверить по токену, сгенерирован ли он на основе данного ключа или нет.
|
||
|
||
Для каждого токена нужно дополнительное случайное значение, которое называют "соль" `salt`.
|
||
|
||
Формула вычисления токена:
|
||
```
|
||
token = salt + ":" + MD5(salt + ":" + secret)
|
||
```
|
||
|
||
Например:
|
||
<ol>
|
||
<li>В сессии хранится `secret="abcdef"`, это значение создаётся один раз.</li>
|
||
<li>Для нового токена сгенерируем `salt`, например пусть `salt="1234"`.</li>
|
||
<li>`token = "1234" + ":" + MD5("1234" + ":" + "abcdef") = "1234:5ad02792a3285252e524ccadeeda3401"`.</li>
|
||
</ol>
|
||
|
||
Это значение -- с одной стороны, случайное, с другой -- имея такой `token`, мы можем взять его первую часть `1234` в качестве `salt` и, зная `secret`, проверить по формуле, верно ли он вычислен.
|
||
|
||
Не зная `secret`, невозможно сгенерировать token, который сервер воспримет как правильный.
|
||
|
||
Далее, токен добавляется в качестве скрытого поля к каждой форме, генерируемой на сервере.
|
||
|
||
То есть, "честная" форма для отсылки сообщений, созданная на `http://mail.com`, будет выглядеть так:
|
||
|
||
```html
|
||
<form action="http://mail.com/send" method="POST">
|
||
*!*
|
||
<input type="hidden" name="csrf" value="1234:5ad02792a3285252e524ccadeeda3401">
|
||
*/!*
|
||
<textarea name="message">
|
||
...
|
||
</textarea>
|
||
</form>
|
||
```
|
||
|
||
При её отправке сервер проверит поле `csrf`, удостоверится в правильности токена, и лишь после этого отошлёт сообщение.
|
||
|
||
"Злая страница" при всём желании не сможет сгенерировать подобную форму, так как не владеет `secret`, и токен будет неверным.
|
||
|
||
Такой токен также называют "подписью" формы, которая удостоверяет, что форма сгенерирована именно на сервере.
|
||
|
||
[smart header="Подпись с полями формы"]
|
||
Эта подпись говорит о том, что автор формы -- сервер, но ничего не гарантирует относительно её содержания.
|
||
|
||
Есть ситуации, когда мы хотим быть уверены, что некоторые из полей формы посетитель не изменил самовольно. Тогда мы можем включить в MD5 для формулы токена эти поля, например:
|
||
```
|
||
token = salt + ":" + MD5(salt + ":" + secret + ":" + fields.money)
|
||
```
|
||
|
||
При отправке формы сервер проверит подпись, подставив в неё известный ему `secret` и присланное значение `fields.money`. При несовпадении либо `secret` не тот (хакер), либо `fields.money` изменено.
|
||
[/smart]
|
||
|
||
## Токен и AJAX
|
||
|
||
Теперь перейдём к AJAX-запросам.
|
||
|
||
Что если посылка сообщений в нашем интерфейсе реализуется через XMLHttpRequest?
|
||
|
||
Как и в случае с формой, мы должны "подписать" запрос токеном, чтобы гарантировать, что его содержимое прислано на сервер именно интерфейсом сайта, а не "злой страницей".
|
||
|
||
Здесь возможны варианты, самый простой -- это дополнительная кука.
|
||
|
||
<ol>
|
||
<li>При авторизации сервер устанавливает куку с именем `CSRF-TOKEN`, и пишет в неё токен.</li>
|
||
<li>Код, осуществляющий 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]);
|
||
}
|
||
```
|
||
</li>
|
||
<li>Сервер проверяет, есть ли заголовок и содержит ли он правильный токен.</li>
|
||
</ol>
|
||
|
||
Защита действует потому, что прочитать куку может только JavaScript с того же домена. "Злая страница" не сможет "переложить" куку в заголовок.
|
||
|
||
Если нужно сделать не XMLHttpRequest, а, к примеру, динамически сгенерировать форму из JavaScript -- она также подписывается аналогичным образом, скрытое поле или дополнительный URL-параметр генерируется по куке.
|
||
|
||
|
||
## Итого
|
||
|
||
<ul>
|
||
<li>CSRF-атака -- это когда "злая страница" отправляет форму или запрос на сайт, где посетитель, предположительно, залогинен.
|
||
|
||
Если сайт проверяет только куки, то он такую форму принимает. А делать это не следует, так как её сгенерировал злой хакер.</li>
|
||
<li>Для защиты от атаки формы, которые генерирует `mail.com`, подписываются специальным токеном. Можно не все формы, а только те, которые осуществляют действия от имени посетителя, то есть могут служить объектом атаки.</li>
|
||
<li>Для подписи XMLHttpRequest токен дополнительно записывается в куку. Тогда JavaScript с домена `mail.com` сможет прочитать её и добавить в заголовок, а сервер -- проверить, что заголовок есть и содержит корректный токен.</li>
|
||
<li>Динамически сгенерированные формы подписываются аналогично: токен из куки добавляется как URL-параметр или дополнительное поле.</li>
|
||
</ul>
|
||
|
||
|
||
Чтобы
|
||
|
||
|