В этой версии добавлен функционал для управления безопасностью сайта. Большая часть доработок касается работы с CSRF токенами. Кто не знает, что такое CSRF токены и для чего они нужны, идёт просвящаться. Так вот, данный функционал необходим для защиты вашего сайта от атак с межсайтовой подделкой запросов. Кстати, админка MODX использует эту защиту — для каждого запроса добавляется токен HTTP_MODAUTH.

CSRF токены

Библиотека modHelpers автоматически генерирует CSRF токен при инициализации (при условии, что включен механизм сессий). Этот токен используется для проверки факта, что запрос поступил от пользователя с сайта. Для создания и получения токена используется функция csrf_token(). Также его можно переформировать заново, если передать в функцию первым аргументом TRUE.

Внимание!

В примерах используется шаблонизатор Fenom. Чтобы запускать функции modHelpers в разметке, необходимо разрешить Fenom использовать PHP. Для этого нужно включить системную настройку pdotools_fenom_php.

Использование токена в HTML формах

Разместите в форме скрытый инпут с именем csrf_token. Сделать это можно через вызов функции-хелпера csrf_field().

<form method="POST" action="profile">
    {csrf_field()}  <!-- <input type="hidden" name="csrf_token" value="sf7Y5wgC01vaSck2rc"> -->
    ...
</form>

Использование в заголовках запроса

Ещё один способ передать токен — через специальный заголовок X-CSRF-TOKEN. Для этого можно использовать, например, HTML тег МЕТА в шаблоне:

<meta name="csrf-token" content="{csrf_token()}">

Можно использовать хелпер csrf_meta(), который вставит готовый код тега в шаблон.

Дальше нужно добавить токен к заголовкам запроса. Вот пример для jQuery:

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

Ещё вариант — вместо тега META вставлять в шаблон яваскрипт переменную и её добавлять в заголовки. It's up to you.

Проверка токена

После того, как вы добавили токен в запрос, нужно его проверить. Если у вас установлен компонент Middlewares, то проверку можно делать в глобальном посреднике. Если нет, то место вы можете выбрать сами. Это может быть класс, плагин или сниппет. Вот примеры проверок:

if (request()->checkCsrfToken()) {
    // Токен существует и соответствует токену в сессии  
}

Условия могут быть разными

// Валидация проходит только для залогиненного пользователя
if (is_auth() && request()->checkCsrfToken() {
    // Выполнить запрашиваемые действия
} else {
    // Запрос отклонён
    abort();
}
// Исключение страниц из проверки. Не проверять страницы с адресом site.ru/pages/1, site.ru/pages/2 и т.д.
if (!request()->match('pages/*') && request()->checkCsrfToken() {...}
// Проверять только POST запросы
if (request()->isMethod('POST') && request()->checkCsrfToken() {...}

Просто проверить наличие токена в запросе:

if (request()->getCsrfToken()) {
    // Токен есть
}

Проверка ботов

Ещё одна доработка позволяет проверять кем выполнен запрос — ботом или человеком. Логика очень простая — в системной настройке указываются паттерны юзер-агентов ботов, спайдеров и т.п. Ну а дальше проверяется юзер-агент запроса. Эта возможность будет полезна, если нужно что-то скрыть от роботов. Например, какие-нибудь ссылки или кнопки у формы. Альтернатива всяким nofollow и noindex.

{if request()->isBot()}
    <p>Информация для роботов.</p>
{else}
    <a href="url">Ссылка только для пользователей</a>
{/if}

Заключение

Ну и последний пункт из списка изменений — изменение приоритета загрузки. Я установил его на -1000, чтобы библиотека грузилась самой первой. Теперь всё. В ближайшее время выйдет обновление Middlewares. Хочу записать видео о новых возможностях этого компонента. Надеюсь, получится.

19 ноября 2017, 18:43   804     13

Комментарии ()

  1. Владимир 22 марта 2018, 10:13 # 0
    А каким образом сверяется токен сессии? Насколько мне известно для того чтобы проверить токен, который записывается в сессию, нужно эту сессию стартонуть, иначе конструкция вида:
    if (request()->checkCsrfToken()) {
        // Токен существует и соответствует токену в сессии  
    }
    
    никогда не произведет вхождение, потому что сессия пустая.
    Возможно в Middlewares и стартуется сессия автоматом, но как быть тогда с:
    Если нет, то место вы можете выбрать сами. Это может быть класс, плагин или сниппет.
    1. Сергей Шлоков 22 марта 2018, 11:04 # 0
      Библиотека modHelpers — это дополнение для MODX. А он при инициализации сам стартует сессию. Поэтому Вы легко можете проверять токен в нужном месте. Например, непосредственно перед записью в базу данных.
      1. Владимир 22 марта 2018, 11:21 # 0
        Хм… Тогда если я в теге head добавляю {csrf_meta()}, то по логике на сервере в папке, где хранятся сессии, я должен увидеть созданный файл сессий в котором и будет сгенерированный токен?
        У меня странная ситуация получилась. Прописал как в данном мануале {csrf_meta()} в чанке, где все что касается тега head. Смотрю на сайте даже все создалось и сгенерировался токен. Потом создал плагин на событие OnWebPagePrerender и прописал такое:
        $e = &$modx->event;
        switch ($e->name) {
            case "OnWebPagePrerender":
                if (request()->checkCsrfToken()) {
                    // Токен существует и соответствует токену в сессии  
                    echo "Token Ok";
                } else {
        		//Смотрим на созданный токен в сессии, какой он
                    $token_ses = request()->getCsrfToken();
                    echo "Token: ".$token_ses;
                }
                break;
            default :
                return;
                break;
        }
        
        Смотрю в журнале ошибок и вижу там Token:
        Тоесть Пустой. Лезу на сервер, смотрю в папке sessions а там 0 файлов сессий.
        Запускаю свой проверочный файл где я сам вручную делаю session_start() — все нормально, файлы создаются.
        Ничего понять не могу.
        modhelpers-3.3.0-beta
        1. Сергей Шлоков 22 марта 2018, 11:56 # 0
          По-умолчанию сессии хранятся в базе данных. Для хранения в файлах нужно указать другой хандлер. Это для понимания начальных условий проблемы.

          Судя по всему вы не понимаете механизм токенов. Если коротко — токен не нужно проверять в каждом запросе. Он используется как подпись для важного действия. Как правило когда приходит форма или ajax запрос и нужно выполнить определённые манипуляции.

          Как работает.

          1. Токен генерится в плагине modHelpers.
          2. Проверка токена идет или по специальному заголовку или по данным формы (см. статью). Мета тег нужен для генерации заголовка для ajax запросов, чтобы не морочится с добавлением токена к каждой форме. Прописал один раз и всё. Вместо МЕТА тега можно указать javascript переменную.
          // В шаблоне
          <script>var token = "{csrf_token()";</script>
          
          А затем его указать
          <script>
          $.ajaxSetup({
              headers: {
                  'X-CSRF-TOKEN': token;
              }
          });
          </script>
          
          Таким образом подписывается каждый API запрос. Если Вы их не используете, то нужно просто подписать форму через csrf_field(), а META тег можно не указывать.
          1. Владимир 22 марта 2018, 12:33 # 0
            Так все и сделано. Мета тег + ajaxSetup. Вот только проверка токена не проходит. А именно:
            if (request()->checkCsrfToken()) {
                // Токен существует и соответствует токену в сессии  
            }
            
            Я не использую компонент Middlewares.
            Уже создал плагин на событие OnHandleRequest и все равно токена нет.
            1. Сергей Шлоков 22 марта 2018, 12:41 # 0
              Попробуйте вывести в вашем файле
              dump(request()->header('X-CSRF-TOKEN'));
              dump(csrf_token());
              
              1. Владимир 22 марта 2018, 12:56 # 0
                Вот такое:
                NULL
                string(40) «ytGyMX8F1e2bnEslPFu7qNU3xGRuEUMRwlts1N9L»

                Хотя в хроме я смотрю x-csrf-token в Request Headers не NULL а ytGyMX8F1e2bnEslPFu7qNU3xGRuEUMRwlts1N9L
                1. Сергей Шлоков 22 марта 2018, 13:04 # 0
                  Ну и какие мысли?

                  П.С. Надеюсь, Вы через ajax делали запрос.
                  1. Владимир 22 марта 2018, 17:41 # 0
                    Я уже скопировал все один в один, ваш пример с формой простенькой. Аякс Форм + хук на csrf и тишина.
                    Вызываю форму в чанке вот так:
                    [[!AjaxForm? 
                      &form=`tpl.AjaxForm` 
                      &snippet=`FormIt` 
                      &hooks=`csrf,FormItSaveForm,email`
                      &emailSubject=`Тестовое сообщение`
                      &emailTo=`test@fortest.ua`
                      &emailFrom=`no-reply@mysite.com`
                      &emailTpl=`tpl.email`
                      &validate=`name:minLength=^2^,email:email:required`
                      &validationErrorMessage=`В форме содержатся ошибки!`
                      &successMessage=`Сообщение успешно отправлено`
                    ]]
                    
                    Сам чанк tpl.AjaxForm:
                    <!-- Плейсхолдер ошибки -->
                    <div class="error">[[!+fi.error.csrf]]</div>
                    
                    <form method="post" class="form">
                        {csrf_field()}
                        <div class="form-group">
                            <label for="name">Имя:</label>
                            <input type="text" name="name" id="name" value="[[!+fi.name]]" />
                        </div>
                        <div class="form-group">
                            <label for="email">Email:</label>
                            <input type="email" name="email" id="email" value="[[!+fi.email]]" />
                        </div>
                        <div class="form-buttons">
                            <input type="submit" class="btn btn-default" value="Отправить" />
                        </div>
                    </form>
                    
                    Сниппет csrf:
                    <?php
                    if (request()->checkCsrfToken('post') === false) {
                        // Выставляем плейсхолдер ошибки
                        $hook->addError('csrf','Ошибка! Указан некорректный токен.');
                        return false;
                    }
                    return true;
                    
                    Все же так?
                    1. Сергей Шлоков 22 марта 2018, 20:05 # 0
                      Так если всё правильно, ошибки и не должно быть.
                      1. Владимир 22 марта 2018, 20:45 # 0
                        МОжет я дам доступ глянешь мельком? Хоть все ли настройки правильные?
                        1. Сергей Шлоков 23 марта 2018, 10:47 # 0
                          Т.е. я потратил столько времени на объяснение напрасно?
                          1. Владимир 23 марта 2018, 12:04 # 0
                            Та уже вродь все. Проверки токена срабатывают. Теперь нужно попробовать проверить Acunetix«ом что он скажет.

    Вы должны авторизоваться, чтобы оставлять комментарии.

    Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.