Немного теории
Я полагаю многие знают, что такое CSRF. Но для новичков озвучу. CSRF (Cross Site Request Forgery) - это межсайтовая подделка запросов. И происходит она следующим образом.Допустим, на вашем сайте, назовем его "mysite.com", есть ссылка, которая удаляет сообщение. Ссылка вида - "http://mysite.com/myprofile/delete?message=123".
Злоумышленник может вставить эту ссылку на свой сайт "attackerssite.com", например, в виде картинки -
<img src="http://mysite.com/delete?message=123" />И если Вы зайдете на "attackerssite.com", то будет отправлен запрос на загрузку картинки с вашего сайта, но вместо загрузки картинки будет удалено сообщение. Проблем с авторизацией не будет, так как будет отправлена ваша кука, запрос же отправляется с вашего браузера.
Таким образом возможно подделать абсолютно любой запрос.
Есть разные методы борьбы с этим, но реально работает только один - необходимо с каждым запросом, который изменяет данные, посылать секретную строку.
Эта строка должны быть уникальна для каждой сессии.
Тогда, ссылка "http://mysite.com/myprofile/delete?message=123" превратится в
"http://mysite.com/myprofile/delete?message=123&secret=af39hf029h3uwe0183y12fnsd". Поскольку "secret" для каждой сессии свой, то злоумышленник его не знает и не может сформировать опасную ссылку.
Способы, которые не работают:
- Использование POST. Наивно полагать, что такой вариант поможет. Злоумышленник может без проблем засабмитить форму со своего сайта при помощи JavaScript
- Проверка по HTTP Referrer. Этот вариант лучше, но все равно не обеспечивает должного уровня безопасности. Во-первых, HTTP Referrer можно подделать в некоторых версиях Flash, во вторых у вашего сайта могу возникнут проблемы при работе через прокси-сервера.
- Проверка по заголовку запроса "X-Requested-With". Раньше считалось надежным вариантом, но сегодня существуют способы обхода.
Практика (Mojolicious)
Меня всегда смущало то, что в базовых плагинах Mojolicious нет защиты от CSRF. Я как-то оформил feature request, но Себастьян предложил мне написать плагин самому. Я этим и занялся :). Замему, что на CPAN существовал ( и сейчас существуюет ) плагин "Mojolicious::Plugin::CSRFDefender", но у него есть ряд недостатков:
Работает он следующим образом:
Дополнительно есть возможность валидировать GET-запросы вручную, например:
В шаблоне:
- Он не защищает при AJAX-запросах. Я запостил реквест автору, но ответа не получил.
- Он грубо работает. Этот плагин парсит каждый ответ клиенту и пытается там найти что-то похожее на html-форму. И если, что-то находит, то вставляет туда скрытый параметр.
- Защищает только POST запросы
Работает он следующим образом:
- Подменяет "form_for" хелпер с Mojolicious::Plugin::TagHerlpers на защищенную версию, которая вставляет в форму скрытый параметр с секретным маркером. Для обеспечения защиты, Вы должны использовать хелпер "form_for".
- Добавляет к каждому AJAX запросу заголовок X-CSRF-Token с секретным маркером в качестве значения.(пока работает только для JQuery)
- Отклоняет(с 403 ошибкой) все не GET запросы с неправильным секретным маркером
<%= jquery_ajax_csrf_protection %>Этот хелпер вставляет следующего рода код:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<meta name="csrftoken" content="af58af65a8f65a8dfafa76df8a7afdf"/> | |
<script type="text/javascript"> | |
$(document).ajaxSend(function(e, xhr, options) { | |
var token = $("meta[name='csrftoken']").attr("content"); | |
xhr.setRequestHeader("X-CSRF-Token", token); | |
}) | |
</script> |
Дополнительно есть возможность валидировать GET-запросы вручную, например:
В шаблоне:
<a href='/logout?csrftoken=<%= csrftoken %>' >В контроллере:
if ( $self->is_valid_csrftoken() ) { # do logout here }
16 коммент.:
Хм странно, почему такие как мне кажеться важные вещи, которые должны быть стандартом, автор не хочет включить в основу фреймворка, он вообще ниодного пулл запроса не принял насколько я видел
Согласен, мне тоже кажется, что защита от CSRF должна идти, как core-плагин. Но увы, пришлось решать эту проблему самому.
Такой сценарий возможен для обхода защиты?
1) Злоумышленник при помощи JavaScript скачивает страницу с формой
2) Достает значение csrftoken (secret) из формы
3) Формирует управляющий запрос используя полученное значение
2Александр:
Такой сценарий не сработает, поскольку кросс-доменно подгрузить форму при помощи AJAX не получится. Возможно, конечно, подгрузить страницу в iframe, но получить к странице доступ с другого домена тоже не получится.
Обход защиты возможен, если злоумышленнику удастся разместить свой javascript-код на странице атакуемого сайта, но тогда он сможет и cookie без проблем переслать.
Внезапно плагин перестал сохранять токен в сессию. Точнее, он вызывает Controller::session, но при считывании получает undef и вообще при считывании происходит мистика - весть стэш пустой. Не знаю, куда копать, пришлось пока что отключить плагин.
Mojolicious 2.51 (не лайт)
Mojolicious::Plugin::CSRFProtect 0.08
Mojolicious::Plugin::BasicAuth 0.06
Mojolicious::Plugin::Validator 0.0013
Проверил под Morbo и Starman.
Очень странно, у меня этот плагин постоянно в использовании и все работает.
Окружение - Mojolicious 2.51(Не лайт) + много разных плагинов.
Возможно поможет очистка cookie.
Нет, не помогла. И воспроизводится в трех основных браузерах. Могу попытаться составить минимальное приложение, в котором воспроизводится проблема.
Да, минимальное приложение для тестирования проблемы просто необходимо, чтобы я мог воспроизвести и исправить ошибку.
Сразу можете создать тикет на гитхабе
Вопрос.
Если приходит запрос без токена, то генерируется страница "Forbidden!" как это событие перехватывать глобально в приложении?
В каждом контроллере делать проверку не удобно, а вот глобальный callback было бы удобно.
Запрос без токена - невалидный запрос и его можно просто игнорировать. Я не совсем понимаю зачем его перехватывать?
2koorchik: например чтобы сохранить в лог информацию о том откуда пришел человек с таким запросом (мало ли понадобится ловить "тестировщиков"), не выводить "Forbidden" а вывести красивое сообщение об ошибке или просто переадресовать на эту же форму еще раз.
2Sergey Malochinskiy:
Я думал добавить параметр "error_template", но колбек будет более универсален конечно.
Добавлю, наверное, что-то типа:
on_error => sub {
my $c = shift;
# Do what you want here.
}
Один минус колбека - его не описать в конфиг файле.
2koorchik:
Спасибо. Буду ждать релиза. :)
Опциональный колбек и шаблон конечно отрисует если надо, а вот из конфигурации да не описать.
Залил свежую версию на СPAN с поддержкой "on_error" callback.
2koorchik:
Потрясающе оперативно.
Я едва успел в TODO запись сделать, а тут уже новая версия. Спасибо!
Автор фреймворка прислушался к вам, спасибо.
http://mojolicio.us/perldoc/Mojolicious/Guides/Rendering#Cross-site_request_forgery
Отправить комментарий
Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)