Как установить CSRF cookie на тот же сайт в CakePHP 3.4?

#php #cakephp

#php #cakephp

Вопрос:

Я использую CakePHP 3.4 (не могу обновиться), и для защиты системы от подделки межсайтовых запросов мне нужно установить для CSRF token cookie значение SameSite = Strict. Однако, похоже, эта версия CakePHP не может обработать такую настройку.

Я попытался использовать класс CsrfComponent и загрузить компонент в AppController

 $this->loadComponent('Csrf', [
            'secure' => true,
            'httpOnly' => true,
        ]);
 

Как я могу обойти установку этого cookie на SameSite = Strict или другую альтернативу для защиты от подделки межсайтовых запросов?

Комментарии:

1. Зависит от а) вашей версии PHP и б) ваших возможных будущих планов по обновлению? Кроме того, что именно мешает вам обновиться хотя бы до последней версии 3.x?

2. В настоящее время на PHP 7.0 anc не может обновить CakePHP, поскольку проект большой и слишком старый, обновление займет много человеческих часов, адаптируя все, что устарело.

Ответ №1:

В CakePHP 3.9.3 добавлена поддержка samesite с CSRF cookies, однако вам придется переключиться на промежуточное программное обеспечение защиты CSRF.

Если вы не можете выполнить обновление, тогда вам понадобится немного пользовательского кода, а именно пользовательский / расширенный компонент CSRF, который принимает дополнительные параметры для атрибута, и пользовательский / расширенный объект ответа, который создает файлы cookie с этим атрибутом соответственно.

В версиях PHP, более ранних, чем PHP 7.3, вы можете, соответственно, должны вводить SameSite атрибут, используя взлом пути к cookie, который состоит в добавлении дополнительных атрибутов cookie к пути, просто закрыв путь точкой с запятой. В версиях PHP, начиная с PHP 7.3, вы бы использовали значение, которое тогда поддерживалось samesite для setcookie() .

кстати, для сеансовых файлов cookie вы должны соответствующим образом изменить свои session.cookie_path или session.cookie_samesite PHP INI параметры, и другие места в CakePHP, которые устанавливают файлы cookie, возможно, также потребуется адаптировать, например, компонент cookie, даже если ваше приложение его не использует, он может использоваться сторонними плагинами.

Пример:

 <?php
// in src/Controller/Component/CsrfComponent.php
namespace AppControllerComponent;

use CakeControllerComponentRegistry;
use CakeHttpResponse;
use CakeHttpServerRequest;

class CsrfComponent extends CakeControllerComponentCsrfComponent
{
    public function __construct(ComponentRegistry $registry, array $config = [])
    {
        // Use Lax by default
        $config  = [
            'samsite' => 'Lax',
        ];
    
        parent::__construct($registry, $config);
    }
    
    protected function _setCookie(ServerRequest $request, Response $response)
    {
        parent::_setCookie($request, $response);

        // Add samesite option to the cookie that has been created by the parent
        $cookie = $response->cookie($this->getConfig('cookieName'));
        $cookie['samesite'] = $this->getConfig('samesite');

        $response->cookie($cookie);
    }
}
 

https://github.com/cakephp/cakephp/blob/3.4.14/src/Controller/Component/CsrfComponent.php#L125

 // in src/Http/Response.php
namespace AppHttp;

class Response extends CakeHttpResponse
{
    protected function _setCookies()
    {
        foreach ($this->_cookies as $name => $c) {
            if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                // Use regular syntax (with possible path hack) in case
                // no samesite has been set, or the PHP version doesn't
                // support the samesite option.
                
                if (isset($c['samesite'])) {
                    $c['path'] .= '; SameSite=' . $c['samesite'];
                }
                
                setcookie(
                    $name,
                    $c['value'],
                    $c['expire'],
                    $c['path'],
                    $c['domain'],
                    $c['secure'],
                    $c['httpOnly']
                );
            } else {
                setcookie($name, $c['value'], [
                    'expires' => $c['expire'],
                    'path' => $c['path'],
                    'domain' => $c['domain'],
                    'secure' => $c['secure'],
                    'httponly' => $c['httpOnly'],
                    'samesite' => $c['samesite'],
                ]);
            }
        }
    }
}
 

https://github.com/cakephp/cakephp/blob/3.4.14/src/Http/Response.php#L540

Введите пользовательский объект ответа в свой AppController конструктор:

 // in src/Controller/AppController.php

use CakeHttpResponse;
use CakeHttpServerRequest;

// ...

class AppController extends Controller
{
    // ...

    public function __construct(
        ServerRequest $request = null,
        Response $response = null,
        $name = null,
        $eventManager = null,
        $components = null
    ) {
        if ($response !== null) {
            throw new InvalidArgumentException(
                'This should not happen, we want to use a custom response class.'
            );
        }
        $response = new AppHttpResponse();
    
        parent::__construct($request, $response, $name, $eventManager, $components);
    }

    // ...
}
 

Наконец, создайте псевдоним Csrf компонента с помощью пользовательского класса component и задайте свою samesite конфигурацию:

 // in src/Controller/AppController.php

// ...

class AppController extends Controller
{
    // ...
    
    public function initialize()
    {
        parent::initialize();

        // ...
        $this->loadComponent('Csrf', [
            'className' => AppControllerComponentCsrfComponent::class,
            'secure' => true,
            'httpOnly' => true,
            'samesite' => 'Strict',
        ]);
    }

    // ...
}
 

В заключение следует отметить, что в более поздних версиях CakePHP 3.x массивы файлов cookie были заменены объектами cookie, что потребовало бы соответствующих изменений, но поскольку вы не можете обновиться, это должно быть нормально, и как только вы решите обновиться, стреляйте по звездам и обновитесь допоследняя версия.