вторник, 1 ноября 2011 г.

Mojolicious и защита от Cross Site Request Forgery (CSRF)

Немного теории
Я полагаю многие знают, что такое 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" для каждой сессии свой, то злоумышленник его не знает и не может сформировать опасную ссылку.

Способы, которые не работают:
  1. Использование POST. Наивно полагать, что такой вариант поможет. Злоумышленник может без проблем засабмитить форму со своего сайта при помощи JavaScript
  2. Проверка по HTTP Referrer. Этот вариант лучше, но все равно не обеспечивает должного уровня безопасности. Во-первых, HTTP Referrer можно подделать в некоторых версиях Flash, во вторых у вашего сайта могу возникнут проблемы при работе через прокси-сервера.
  3. Проверка по заголовку запроса "X-Requested-With".  Раньше считалось надежным вариантом, но сегодня существуют способы обхода.
Более подробную информацию можно найти в итернетах.

Практика (Mojolicious)
Меня всегда смущало то, что в базовых плагинах Mojolicious нет защиты от CSRF. Я как-то оформил feature request, но Себастьян предложил мне написать плагин самому. Я этим и занялся :). Замему, что на CPAN существовал ( и сейчас существуюет ) плагин  "Mojolicious::Plugin::CSRFDefender", но у него есть ряд недостатков:
  1. Он не защищает при AJAX-запросах. Я запостил реквест автору, но ответа не получил.
  2. Он грубо работает. Этот плагин парсит каждый ответ клиенту и пытается там найти что-то похожее на html-форму. И если, что-то находит, то вставляет туда скрытый параметр.
  3. Защищает только POST запросы
В связи с этим был написан новый плагин "Mojolicious::Plugin::CSRFProtect", лишенный этих недостатков.
Работает он следующим образом:
  1. Подменяет "form_for" хелпер с Mojolicious::Plugin::TagHerlpers на защищенную  версию, которая вставляет в форму скрытый параметр с секретным маркером. Для обеспечения защиты, Вы должны использовать хелпер "form_for".
  2. Добавляет к каждому AJAX запросу заголовок X-CSRF-Token с секретным маркером в качестве значения.(пока работает только для JQuery)
  3. Отклоняет(с 403 ошибкой) все не GET запросы с неправильным секретным маркером
Для того, чтобы включить защиту AJAX-запросов, необходимо в основной шаблон добавить
<%= jquery_ajax_csrf_protection %>
Этот хелпер вставляет следующего рода код:


Дополнительно есть возможность валидировать GET-запросы вручную, например:
В шаблоне:
<a href='/logout?csrftoken=<%= csrftoken %>' >
В контроллере:
if ( $self->is_valid_csrftoken() ) {
#    do logout here
}

8 коммент.:

fuksito комментирует...

Хм странно, почему такие как мне кажеться важные вещи, которые должны быть стандартом, автор не хочет включить в основу фреймворка, он вообще ниодного пулл запроса не принял насколько я видел

koorchik комментирует...

Согласен, мне тоже кажется, что защита от CSRF должна идти, как core-плагин. Но увы, пришлось решать эту проблему самому.

Александр комментирует...

Такой сценарий возможен для обхода защиты?
1) Злоумышленник при помощи JavaScript скачивает страницу с формой
2) Достает значение csrftoken (secret) из формы
3) Формирует управляющий запрос используя полученное значение

koorchik комментирует...

2Александр:
Такой сценарий не сработает, поскольку кросс-доменно подгрузить форму при помощи AJAX не получится. Возможно, конечно, подгрузить страницу в iframe, но получить к странице доступ с другого домена тоже не получится.

Обход защиты возможен, если злоумышленнику удастся разместить свой javascript-код на странице атакуемого сайта, но тогда он сможет и cookie без проблем переслать.

afiskon комментирует...

Внезапно плагин перестал сохранять токен в сессию. Точнее, он вызывает Controller::session, но при считывании получает undef и вообще при считывании происходит мистика - весть стэш пустой. Не знаю, куда копать, пришлось пока что отключить плагин.

Mojolicious 2.51 (не лайт)
Mojolicious::Plugin::CSRFProtect 0.08
Mojolicious::Plugin::BasicAuth 0.06
Mojolicious::Plugin::Validator 0.0013

Проверил под Morbo и Starman.

koorchik комментирует...

Очень странно, у меня этот плагин постоянно в использовании и все работает.
Окружение - Mojolicious 2.51(Не лайт) + много разных плагинов.

Возможно поможет очистка cookie.

afiskon комментирует...

Нет, не помогла. И воспроизводится в трех основных браузерах. Могу попытаться составить минимальное приложение, в котором воспроизводится проблема.

koorchik комментирует...

Да, минимальное приложение для тестирования проблемы просто необходимо, чтобы я мог воспроизвести и исправить ошибку.

Сразу можете создать тикет на гитхабе

Отправить комментарий

Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)