Как реализовать защиту от CSRF

CSRF - или Межсайтовая подделка запроса - это метод, которым недобросовестный пользователь пытается заставить ваших пользователей не зная об этом, отправлять данные, которые они не собирались отправлять.

Защита от CSRF работает путём добавления ввашу форму скрытого поля, которое содержит значение, которое знаете только вы и ваш пользователь. Это гарантирует, что пользователь - а не какая-то другая сущность - отправляет данные.

Перед использованием защиты от CSRF, установите её в вашем проекте (что, в свою очередь, требует установки компонента Symfony Форма):

1
$ composer require security-csrf form

Then, enable/disable the CSRF protection with the csrf_protection option (see the CSRF configuration reference for more information):

  • YAML
    1
    2
    3
    4
    # config/packages/framework.yaml
    framework:
        # ...
        csrf_protection: ~
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- config/packages/framework.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:csrf-protection enabled="true" />
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    // config/packages/framework.php
    $container->loadFromExtension('framework', array(
        'csrf_protection' => null,
    ));
    

Защита от CSRF в формах Symfony

Формы, созданные с помощью компонента Symfony Форма включают в себя токены CSRF по умолчанию, и Symfony проверяет их автоматически, так что вам не нужно ничего делать, чтобы быть защищёнными от CSRF атак.

По умолчанию, Symfony добавляет CSRF токен в скрытое поле под названием _token, но это можно настраивать от формы к форме:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ...
use App\Entity\Task;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TaskType extends AbstractType
{
    // ...

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class'      => Task::class,
            // включить/выключить защиту от CSRF для этой формы
            'csrf_protection' => true,
            // имя скрытого HTML поля, хранящего токен
            'csrf_field_name' => '_token',
            // произвольная строка, использовання для генерирования значения токена,
            // использование другой строки для каждой формы усиливает её безопасность
            'csrf_token_id'   => 'task_item',
        ));
    }

    // ...
}

Caution

Так как токен хранится в сессии, то она начинается автоматически, как только вы отображаете форму с защитой от CSRF.

Caution

CSRF токены должны отличаться для каждого пользователя. Имейте это в виду при кешировании страниц, включащих в себя формы, содержащие CSRF токены. Чтобы узнать больше, см. Caching Pages that Contain CSRF Protected Forms.

Зщаита от CSRF в формах входа в систему

CSRF атаки входа в систему могут быть предотвращены, используя ту же технику добавления скрытых CSRF токенов в формы входа. Компонент Безопасность уже предоставляет защиту от CSRF, но вам нужно сконфигурировать некоторые опции до того, как её использовать.

Tip

Если вы используете Аутентификатор Guard, то вам понадобится валидировать CSRF токен вручную внутри этого класса. См. Adding CSRF Protection, чтобы узнать детали.

Для начала, сконфигурируйте поставщика CSRF токена, используемого формой входа в вашей конфигурации безопасности. Вы можете установить его так, чтобы использовался доступный по умолчанию поставщик компонента Безопасность:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/packages/security.yaml
    security:
        # ...
    
        firewalls:
            secured_area:
                # ...
                form_login:
                    # ...
                    csrf_token_generator: security.csrf.token_manager
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/packages/security.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="secured_area">
                <!-- ... -->
                <form-login csrf-token-generator="security.csrf.token_manager" />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // config/packages/security.php
    $container->loadFromExtension('security', array(
        // ...
    
        'firewalls' => array(
            'secured_area' => array(
                // ...
                'form_login' => array(
                    // ...
                    'csrf_token_generator' => 'security.csrf.token_manager',
                ),
            ),
        ),
    ));
    

Далее, используйте функцию csrf_token() в шаблоне Twig, чтобы сгенерироват CSRF токен и сохраните его как скрытое поле формы. По умолчанию, HTML поле должно иметь имя _csrf_token, а строка, использовання для генерирования значения, должна быть authenticate:

  • Twig
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    {# templates/security/login.html.twig #}
    
    {# ... #}
    <form action="{{ path('login') }}" method="post">
        {# ... the login fields #}
    
        <input type="hidden" name="_csrf_token"
            value="{{ csrf_token('authenticate') }}"
        >
    
        <button type="submit">login</button>
    </form>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- templates/security/login.html.php -->
    
    <!-- ... -->
    <form action="<?php echo $view['router']->path('login') ?>" method="post">
        <!-- ... the login fields -->
    
        <input type="hidden" name="_csrf_token"
            value="<?php echo $view['form']->csrfToken('authenticate') ?>"
        >
    
        <button type="submit">login</button>
    </form>
    

После этого, вы защитили вашу форму входа от CSRF атак.

Tip

Вы можете менять имя поля, установив csrf_parameter, и изменять ID токена, установив csrf_token_id в вашей конфигурации:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # config/packages/security.yaml
    security:
        # ...
    
        firewalls:
            secured_area:
                # ...
                form_login:
                    # ...
                    csrf_parameter: _csrf_security_token
                    csrf_token_id: a_private_string
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- config/packages/security.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="secured_area">
                <!-- ... -->
                <form-login csrf-parameter="_csrf_security_token"
                    csrf-token-id="a_private_string"
                />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/packages/security.php
    $container->loadFromExtension('security', array(
        // ...
    
        'firewalls' => array(
            'secured_area' => array(
                // ...
                'form_login' => array(
                    // ...
                    'csrf_parameter' => '_csrf_security_token',
                    'csrf_token_id'  => 'a_private_string',
                ),
            ),
        ),
    ));
    

Защита от CSRF в HTML формах

Также возможно добавлять защиту от CSRF к обычным HTML формам, не управляемых компонентом Symfony Формы. Например, к простым формам, использумым для удаления объектов. Для начала, используйте функцию csrf_token() в шаблоне Twig, чтобы сгенерировать 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
use Symfony\Component\HttpFoundation\Request;
// ...

public function delete(Request $request)
{
    $submittedToken = $request->request->get('token');

    // 'delete-item' - это то же значение, что использовалось в щаблоне для генерирования токена
    if ($this->isCsrfTokenValid('delete-item', $submittedToken)) {
        // ... сделайте что-то, например, удалите объект
    }
}

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.