Немного теории
Я полагаю многие знают, что такое 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 %>Этот хелпер вставляет следующего рода код:
Дополнительно есть возможность валидировать 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 каналом ;)