Как реализовать CSRF-защиту
Дата обновления перевода 2025-02-15
Как реализовать CSRF-защиту
CSRF - или Межсайтовая подделка запроса - это тип атаки, при которой злоумышленник обманом заставляет пользователя выполнять действия в веб-приложении без его ведома или согласия.
Атака основана на доверии веб-приложения к браузеру пользователя
(например, к куки сессии). Вот реальный пример CSRF-атаки: злоумышленник
может создать следующий веб-сайт:
1 2 3 4 5 6 7 8 9 10 11 12
<html>
<body>
<form action="https://example.com/settings/update-email" method="POST">
<input type="hidden" name="email" value="malicious-actor-address@some-domain.com"/>
</form>
<script>
document.forms[0].submit();
</script>
<!-- какое-то содержание для отвлечения внимания пользователя -->
</body>
</html>
Если вы посетите этот сайт (например, перейдя по ссылке в электронной почте или в социальной
сети) и уже были авторизованы на сайте https://example.com
,
злоумышленник может изменить адрес электронной почты, связанный с вашей учетной записью
(фактически завладев вашей учетной записью) без вашего ведома.
Эффективным способом предотвращения CSRF-атак является использование анти-CSRF-токенов. Это уникальные маркеры, добавляемые в формы в качестве скрытых полей. Легитимный сервер проверяет их, чтобы убедиться, что запрос поступил от ожидаемого источника, а не от какого-то другого вредоносного сайта.
Установка
Symfony предоставляет все необходимые функции для генерирования и валидации анти-CSRF токенов. Прежде чем использовать их, установите этот пакет в своем проекте:
1
$ composer require symfony/security-csrf
Затем включите/отключите защиту CSRF с помощью опции csrf_protection
.
(см. справочник конфигурации CSRF
для получения дополнительной информации):
1 2 3 4
# config/packages/framework.yaml
framework:
# ...
csrf_protection: ~
Токены, используемые для CSRF-защиты, должны быть разными для каждого пользователя, и они хранятся в сессии. Поэтому сессия начинается автоматически, как только вы отображаете форму с CSRF-защитой.
Более того, это означает, что вы не можете полностью кешировать страницы, которые имеют формы с защитой от CSRF. Как вариант, вы можете:
- Встроить форму внутрь некешируемого фрагмента ESI и кешировать остаток содержания страницы;
- Кешировать всю страницу и загрузить форму через некешируемый запрос AJAX;
- Кешировать всю страницу и использовать hinclude.js для загрузки CSRF-токена с некешируемым запросом AJAX, и заменить значение поля формы им.
CSRF-защита в формах Symfony
Формы Symfony включают в себя токены CSRF по умолчанию, и Symfony проверяет их автоматически, так что вам не нужно ничего делать, чтобы быть защищёнными от CSRF атак.
По умолчанию Symfony добавляет CSRF-токен в скрытое поле под названием _token
, но
это можно настроить (1) глобально для всех форм и (2) отдельно для каждой формы.
Глобально вы можете сконфигурировать это в опции framework.form
:
1 2 3 4 5 6 7
# config/packages/framework.yaml
framework:
# ...
form:
csrf_protection:
enabled: true
field_name: 'custom_token_name'
Для каждой отдельной формы вы можете сконфигурировать защиту от CSRF в методе setDefaults()
.
каждой формы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Form/TaskType.php
namespace App\Form;
// ...
use App\Entity\Task;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TaskType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Task::class,
// включить/отключить защиту от CSRF для этой формы
'csrf_protection' => true,
// имя скрытого HTML-поля, в котором хранится токен
'csrf_field_name' => '_token',
// произвольная строка, используемая для генерирования значения токена
// использование разных строк для каждой формы повышает ее безопасность
'csrf_token_id' => 'task_item',
]);
}
// ...
}
Вы также можете настроить отображение поля формы CSRF, создав пользовательскую
тему формы и используя csrf_token
в качестве префикса
поля (например, определить {% block csrf_token_widget %} ... {% endblock %}
, чтобы
настроить все содержание поля формы).
Защита от CSRF в форме входа и действии выхода из системы
Прочтите следующее:
- Защита от CSRF в формах входа ;
- Защита от CSRF для действия выхода из системы .
Генерирование и проверка CSRF-токенов вручную
Хотя Формы Symfony предоставляют автоматическую CSRF-защиту по умолчанию, вам может понадобиться сгенерировать и проверить CSRF-токены вручную, например, при использовании обычных HTML-форм, не управляемыъ компонентом Symfony Формы.
Рассмотрите HTML-форму, созданную для позволения удаления объектов. Для начала, используйте функцию Twig csrf_token() , чтобы сгенерировать CSRF-токен в шаблоне и сохранить его в скрытом поле формы:
1 2 3 4 5 6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
{# аргумент csrf_token() это произвольная строка. используемая для генерирования токена #}
<input type="hidden" name="token" value="{{ csrf_token('delete-item') }}">
<button type="submit">Delete item</button>
</form>
Затем, получите значение CSRF-токена в действии контроллера и используйте метод isCsrfTokenValid() чтобы проверить его валидность:
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ...
public function delete(Request $request): Response
{
$submittedToken = $request->getPayload()->get('token');
// 'delete-item' это то же значение, что используется в шаблоне для генерирования токена
if ($this->isCsrfTokenValid('delete-item', $submittedToken)) {
// ... сделайте что-то, вроде удаления объекта
}
}
В качестве альтернативы вы можете использовать атрибут IsCsrfTokenValid в действии контроллера:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...
#[IsCsrfTokenValid('delete-item', tokenKey: 'token')]
public function delete(): Response
{
// ... сделать что-то, вроде удаления объекта
}
Suppose you want a CSRF token per item, so in the template you have something like the following:
1 2 3 4 5 6
<form action="{{ url('admin_post_delete', { id: post.id }) }}" method="post">
{# the argument of csrf_token() is a dynamic id string used to generate the token #}
<input type="hidden" name="token" value="{{ csrf_token('delete-item-' ~ post.id) }}">
<button type="submit">Delete item</button>
</form>
Атрибут IsCsrfTokenValid также принимает объект Expression, оцененный по id:
1 2 3 4 5 6 7 8 9 10
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsCsrfTokenValid;
// ...
#[IsCsrfTokenValid(new Expression('"delete-item-" ~ args["post"].getId()'), tokenKey: 'token')]
public function delete(Post $post): Response
{
// ... сделать что-то, вроде удаления объекта
}
7.1
Атрибут IsCsrfTokenValid был представлен в Symfony 7.1.
CSRF-токены и атаки сжатия со сторонних каналов
BREACH и CRIME - это эксплойты безопасности против HTTPS при использовании сжатия HTTP. Хакеры могут использовать информацию, упущенную во время сжатия, чтобы восстановить целевые части открытого текста. Чтобы ослабить эти атаки, и предупредить хакера от укгадывания CSRF-токенов, к началу токена добавляется рандомная маска, которая используется для его перемешивания.