Перейти к содержимому

Защита от CSRF

2.5.x ✅ 4.0.x ✅

Атаки подделки межсайтовых запросов (CSRF) заставляют пользователей выполнять нежелательные действия на сайте, где они аутентифицированы. XOOPS предоставляет встроенную защиту от CSRF через класс XoopsSecurity.

  • Security-Best-Practices - Комплексное руководство по безопасности
  • Input-Sanitization - MyTextSanitizer и валидация
  • SQL-Injection-Prevention - Практики безопасности базы данных

Атака CSRF происходит, когда:

  1. Пользователь аутентифицирован на вашем сайте XOOPS
  2. Пользователь посещает вредоносный веб-сайт
  3. Вредоносный сайт отправляет запрос на ваш сайт XOOPS, используя сеанс пользователя
  4. Ваш сайт обрабатывает запрос так, как если бы он пришёл от законного пользователя

XOOPS предоставляет класс XoopsSecurity для защиты от атак CSRF. Этот класс управляет токенами безопасности, которые должны быть включены в формы и проверены при обработке запросов.

Класс безопасности генерирует уникальные токены, которые хранятся в сеансе пользователя и должны быть включены в формы:

$security = new XoopsSecurity();
// Получение поля HTML с входящим токеном
$tokenHTML = $security->getTokenHTML();
// Получение только значения токена
$tokenValue = $security->createToken();

При обработке отправки формы убедитесь, что токен действителен:

$security = new XoopsSecurity();
if (!$security->check()) {
redirect_header('index.php', 3, _MD_TOKENEXPIRED);
exit();
}

При использовании классов форм XOOPS защита токеном проста:

// Создание формы
$form = new XoopsThemeForm('Add Item', 'form_name', 'submit.php');
// Добавление элементов формы
$form->addElement(new XoopsFormText('Title', 'title', 50, 255, ''));
$form->addElement(new XoopsFormTextArea('Content', 'content', ''));
// Добавление скрытого поля токена - ВСЕГДА включайте это
$form->addElement(new XoopsFormHiddenToken());
// Добавление кнопки отправки
$form->addElement(new XoopsFormButton('', 'submit', _SUBMIT, 'submit'));

Для пользовательских HTML-форм, которые не используют XoopsForm:

// В вашем шаблоне формы или PHP-файле
$security = new XoopsSecurity();
?>
<form method="post" action="submit.php">
<input type="text" name="title" />
<textarea name="content"></textarea>
<!-- Включение токена -->
<?php echo $security->getTokenHTML(); ?>
<button type="submit">Submit</button>
</form>

При генерации форм в шаблонах Smarty:

// В вашем PHP-файле
$security = new XoopsSecurity();
$GLOBALS['xoopsTpl']->assign('token', $security->getTokenHTML());
{* В вашем шаблоне *}
<form method="post" action="submit.php">
<input type="text" name="title" />
<textarea name="content"></textarea>
{* Включение токена *}
<{$token}>
<button type="submit">Submit</button>
</form>
// В вашем скрипте обработки формы
$security = new XoopsSecurity();
// Проверка токена
if (!$security->check()) {
redirect_header('index.php', 3, _MD_TOKENEXPIRED);
exit();
}
// Токен действителен, обработка формы
$title = $_POST['title'];
// ... продолжение обработки
$security = new XoopsSecurity();
if (!$security->check()) {
// Получение подробной информации об ошибке
$errors = $security->getErrors();
// Логирование ошибки
error_log('CSRF token validation failed: ' . implode(', ', $errors));
// Перенаправление с сообщением об ошибке
redirect_header('form.php', 3, 'Security token expired. Please try again.');
exit();
}

При работе с запросами AJAX включите токен в ваш запрос:

// JavaScript - получение токена из скрытого поля
var token = document.querySelector('input[name="XOOPS_TOKEN_REQUEST"]').value;
// Включение в запрос AJAX
fetch('ajax_handler.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'action=save&XOOPS_TOKEN_REQUEST=' + encodeURIComponent(token)
});
// Обработчик PHP AJAX
$security = new XoopsSecurity();
if (!$security->check()) {
echo json_encode(['error' => 'Invalid security token']);
exit();
}
// Обработка запроса AJAX

Для дополнительной защиты, особенно для запросов AJAX, вы также можете проверить заголовок HTTP referer:

$security = new XoopsSecurity();
// Проверка заголовка referer
if (!$security->checkReferer()) {
echo json_encode(['error' => 'Invalid request']);
exit();
}
// Также проверка токена
if (!$security->check()) {
echo json_encode(['error' => 'Invalid token']);
exit();
}
$security = new XoopsSecurity();
// Выполнение обеих проверок
if (!$security->checkReferer() || !$security->check()) {
redirect_header('index.php', 3, 'Security validation failed');
exit();
}

Токены имеют ограниченное время жизни, чтобы предотвратить атаки повтора. Вы можете настроить это в параметрах XOOPS или обработать истёкшие токены корректно:

$security = new XoopsSecurity();
if (!$security->check()) {
// Токен может истечь
// Регенерировать форму с новым токеном
redirect_header('form.php', 3, 'Your session has expired. Please submit the form again.');
exit();
}

Если на странице несколько форм, каждая должна иметь свой токен:

// Форма 1
$form1 = new XoopsThemeForm('Form 1', 'form1', 'submit1.php');
$form1->addElement(new XoopsFormHiddenToken('token1'));
// Форма 2
$form2 = new XoopsThemeForm('Form 2', 'form2', 'submit2.php');
$form2->addElement(new XoopsFormHiddenToken('token2'));

Всегда используйте токены для операций, изменяющих состояние

Заголовок раздела «Всегда используйте токены для операций, изменяющих состояние»

Включайте токены в любую форму, которая:

  • Создаёт данные
  • Обновляет данные
  • Удаляет данные
  • Изменяет параметры пользователя
  • Выполняет любое административное действие

Не полагайтесь исключительно на проверку Referer

Заголовок раздела «Не полагайтесь исключительно на проверку Referer»

Заголовок HTTP referer может быть:

  • Удалён инструментами конфиденциальности
  • Отсутствовать в некоторых браузерах
  • Подделан в некоторых случаях

Всегда используйте проверку токена в качестве основной защиты.

Рассмотрите регенерацию токенов:

  • После успешной отправки формы
  • После входа/выхода
  • Через регулярные промежутки времени для продолжительных сеансов
$security = new XoopsSecurity();
if (!$security->check()) {
// Сохранение данных формы временно
$_SESSION['form_backup'] = $_POST;
// Перенаправление обратно на форму с сообщением
redirect_header('form.php?restore=1', 3, 'Please resubmit the form.');
exit();
}

Проблема: Проверка безопасности не удаётся с ошибкой “token not found”

Решение: Убедитесь, что поле токена включено в вашу форму:

$form->addElement(new XoopsFormHiddenToken());

Проблема: Пользователи видят “token expired” после длительного заполнения формы

Решение: Рассмотрите использование JavaScript для периодического обновления токена:

// Обновление токена каждые 10 минут
setInterval(function() {
fetch('refresh_token.php')
.then(response => response.json())
.then(data => {
document.querySelector('input[name="XOOPS_TOKEN_REQUEST"]').value = data.token;
});
}, 600000);

Проблема: Запросы AJAX не проходят проверку токена

Решение: Убедитесь, что токен передаётся с каждым запросом AJAX и проверяется на стороне сервера:

// Обработчик AJAX
header('Content-Type: application/json');
$security = new XoopsSecurity();
if (!$security->check(true, false)) { // Не очищайте токен для AJAX
http_response_code(403);
echo json_encode(['error' => 'Invalid token']);
exit();
}
form.php
<?php
require_once dirname(__DIR__) . '/mainfile.php';
$security = new XoopsSecurity();
// Обработка отправки формы
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$security->check()) {
redirect_header('form.php', 3, 'Security token expired. Please try again.');
exit();
}
// Обработка корректной отправки
$title = $myts->htmlSpecialChars($_POST['title']);
// ... сохранение в базе данных
redirect_header('success.php', 3, 'Item saved successfully!');
exit();
}
// Отображение формы
$GLOBALS['xoopsOption']['template_main'] = 'mymodule_form.tpl';
include XOOPS_ROOT_PATH . '/header.php';
$form = new XoopsThemeForm('Add Item', 'add_item', 'form.php');
$form->addElement(new XoopsFormText('Title', 'title', 50, 255, ''));
$form->addElement(new XoopsFormTextArea('Content', 'content', ''));
$form->addElement(new XoopsFormHiddenToken());
$form->addElement(new XoopsFormButton('', 'submit', _SUBMIT, 'submit'));
$GLOBALS['xoopsTpl']->assign('form', $form->render());
include XOOPS_ROOT_PATH . '/footer.php';

#security #csrf #xoops #forms #tokens #XoopsSecurity