18
Янв
Некоторые особенности CSRF Protection в symfony
Posted by Jeka under Программирование
В данной заметке я привожу некоторую ситуацию, с которой я столкнулся при работе с формами в замечательном PHP фреймворке Symfony. В частности, проблема возникала при включенной защите от межсайтовых запросов (CSRF Protection), что это за защита вы можете прочитать в википедии (http://ru.wikipedia.org/wiki/CSRF, http://www.inattack.ru/article/552.html).
В симфонии при включенной CSRF защите в форму подставляется скрытое поле с именем _csrf_token, его значение формируется как md5 хеш от секретной строки, имени класса и идентификатора сессии (session_id).
Пример формирования значения токена в Symfony:
// sfForm.class.php
public function getCSRFToken($secret = null)
{
....
return md5($secret.session_id().get_class($this));
}
Следовательно, если после некоторого действия значение, возвращаемое session_id() меняется, то дальнейшая валидация созданных до этого момента форм, не будет корректно обрабатываться.
Такие случаи могут возникать, например, при авторизации пользователя (sfGuardPlugin) и дальнейшей обработке форм в одном запросе.
Пример, у нас есть две формы c полями:
1. sfGuardFormSignin: signin[username], signin[password]
2. AddressForm: address[phone],address[city],… -
Мы хотим в одном запросе авторизовать пользователя с помощью логина пароля и сохранить обязательные поля из формы address
Делаем примерно так:
$this->auth_form = !$this->getUser()->isAuthenticated() ? new sfGuardFormSignin() : null;
$this->address_form = new AddressForm();
if ($request->isMethod('post'))
{
// авторизуемся
if (!$this->getUser()->isAuthenticated())
{
$this->auth_form->bind($request->getParameter('signin'));
if ($this->auth_form->isValid())
{
$values = $this->auth_form->getValues();
$this->getUser()->signin($values['user'], array_key_exists('remember', $values) ? $values['remember'] : false);
$this->auth_form=null;
}
}
// обрабатываем форму адреса
$this->address_form->bind($request->getParameter('address'));
if ($this->address_form->isValid())
{
// что-то делаем с формой, например сохраняем
$this->address_form->save();
//...
$this->redirect('@somepath');
}
}
Допустим, авторизация прошла успешно, но валидация формы адреса не прошла. Тогда нам покажется форма адреса с ошибками, но исправив ошибки мы все равно получим не валидную форму так как session_id изменился, а форма адреса создавалась с учетом старого его значения и
нам будет в любом случае выдавать ошибку «csrf token: Required.».
Как избежать подобного?
Способ который я применил (на мой взгляд не очень красивый) заключается в следующем: нужно после авторизации поставить в сессию атрибут
о временном отключении «CSRF Protetion» перед обработкой форм проверять данный атрибут и отключать защиту CSRFT.
Пример:
$this->auth_form = !$this->getUser()->isAuthenticated() ? new sfGuardFormSignin() : null;
$this->address_form = new AddressForm();
if ($request->isMethod('post'))
{
// Авторизация
if (!$this->getUser()->isAuthenticated())
{
$this->auth_form->bind($request->getParameter('signin'));
if ($this->auth_form->isValid())
{
$values = $this->auth_form->getValues();
$this->getUser()->signin($values['user'], array_key_exists('remember', $values) ? $values['remember'] : false); // <<< здесь меняется session_id
$this->getUser()->setAttribute('disable_csrf',true); // <<< снимаем защиту CSRF
$this->auth_form=null;
}
}
// Отключаем защиту если требуется
if ($this->getUser()->getAttribute('disable_csrf',false))
{
sfForm::disableCSRFProtection();
unset ($this->address_form[sfForm::getCSRFFieldName()]);
}
// обработка формы адреса
$this->address_form->bind($request->getParameter('address'));
if ($this->address_form->isValid())
{
// что-то делаем с формой, например сохраняем
$this->address_form->save();
//...
$this->getUser()->setAttribute('disable_csrf',null); // если все хорошо включаем защиту CSRF обратно
$this->redirect('@somepath');
}
}
Все вышеприведенное тестировалось на версии symfony 1.2, в этой версии CSRF защиту можно отключить только глобально. В более новых версиях фреймворка (1.3, 1.4), появилась возможность отключать защиту локально для конкретной формы по отдельности, что более правильно.
З.Ы. Если кто-то скажет, как подобное можно более красиво решить, буду очень благодарен
