Protection CSRF (Origin + jeton scopé)
Зачем защищать POST
Без CSRF-токена, привязанного к сессии, сторонний сайт может отправить POST к PmaControl, пока аутентифицированный пользователь работает на другом сайте. Браузер автоматически прикладывает cookies сессии, и действие выполняется без воли пользователя — изменение конфигурации, запуск операции, inline-обновление.
Общий хелпер Glial
Начиная с Glial v5.1.40, логика CSRF и валидация Origin/Referer вынесены во фреймворк и используются всеми контроллерами PmaControl:
Glial\Security\Csrf::issueToken()/Csrf::validateToken()— выдача и проверка токена по функциональному scope.Glial\Http\Request::isSameSite()— сравнивает Origin (в приоритете), затем Referer (fallback) с текущим origin.
Worker.php содержит только HTTP-оркестрацию, специфичную для POST /Worker/update.Схема процесса
Выдача токена
Перед рендерингом представления с формой или inline-editable ячейкой контроллер выдаёт токен, привязанный к функциональному scope:
<?php
use Glial\Security\Csrf;
$token = Csrf::issueToken($_SESSION, 'worker.update');
$this->set('csrf_token', $token);
worker.update, быть повторно использованным на daemon.start. Токен хранится в $_SESSION['csrf_tokens'][$scope] и сохраняется до конца сессии пользователя.Вставка на клиенте
Для inline-editable ячеек (bootstrap-editable) токен передаётся через два data-* атрибута:
<td class="editable"
data-name="nb_worker"
data-csrf-field="csrf_token"
data-csrf-token="<?= htmlspecialchars($csrf_token, ENT_QUOTES, 'UTF-8') ?>">
5
</td>
htmlspecialchars(..., ENT_QUOTES, 'UTF-8'), чтобы нейтрализовать любую HTML/JS-инъекцию в ячейке.JS App/Webroot/js/Tree/index.js обнаруживает наличие data-csrf-token и автоматически добавляет токен в параметры POST, отправляемые bootstrap-editable:
$.fn.editable.defaults.params = function (params) {
var $cell = $(this).closest('[data-csrf-token]');
if ($cell.length) {
params[$cell.data('csrf-field')] = $cell.data('csrf-token');
}
return params;
};
window.pmacontrolInitLineEdit(context) вызывается повторно для перезагруженного контекста, чтобы связать новый токен с новыми ячейками.Цепочка валидации на сервере
Четыре проверки выполняются строго в этом порядке, до построения SQL:
- HTTP-метод — только
POST; любой другой возвращает405 Method Not Allowed. - Same-site origin — сначала
Origin, затемRefererкак fallback; значенияnull, protocol-relative и с другим scheme/host/port отклоняются с403 Invalid request origin. - CSRF-токен — сравнение
hash_equals()между полемcsrf_tokenв payload и$_SESSION['csrf_tokens'][$scope]; отсутствующий или неверный токен возвращает403 Invalid CSRF token. - Allowlist payload — только ожидаемые поля и типы для этого endpoint; payload вне списка возвращает
400 Invalid worker update payload.
<?php
class Worker
{
public function update()
{
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
return;
}
if (!Glial\Http\Request::isSameSite($_SERVER)) {
http_response_code(403);
echo 'Invalid request origin';
return;
}
if (!Glial\Security\Csrf::validateToken($_POST, $_SESSION, 'worker.update')) {
http_response_code(403);
echo 'Invalid CSRF token';
return;
}
$sql = $this->buildWorkerUpdateSql($_POST);
if ($sql === null) {
http_response_code(400);
echo 'Invalid worker update payload';
return;
}
// SQL exécuté ici uniquement
}
}
Справочник — коды возврата
| Код | Причина | Когда |
|---|---|---|
405 |
Method Not Allowed | HTTP-метод, отличный от POST. |
403 |
Invalid request origin | Origin/Referer вне сайта, Origin: null, protocol-relative или другой scheme/host/port. |
403 |
Invalid CSRF token | Токен отсутствует в payload или не совпадает с $_SESSION['csrf_tokens'][$scope]. |
400 |
Invalid worker update payload | Поле вне allowlist, нецелое значение, отрицательный или нулевой pk. |
Документированные исключения
Хелпер CSRF покрывает только web-POST. Следующие поверхности явно исключены:
- CLI
php glial …— нет cookie сессии, нет CSRF-поверхности. - REST API machine-to-machine — аутентификация отдельным API-токеном; проверка Origin/Referer отключена для этих маршрутов, исключение документируется по endpoint.
- Read-only endpoints — переведены на GET и потому вне периметра CSRF.
Выделенные тесты
Защита CSRF покрыта тестами PHPUnit, которые явно воспроизводят исторический сценарий атаки:
./vendor/bin/phpunit tests/Glial/Security/CsrfTest.php
./vendor/bin/phpunit tests/Glial/Http/RequestTest.php
./vendor/bin/phpunit tests/Controller/WorkerUpdateSecurityTest.php
Origin: https://attacker.test или любым другим внешним сайтом payload отклоняется с 403 Invalid request origin до любой записи, даже если предоставлен валидный CSRF-токен — цепочка валидации останавливается на Origin до проверки токена.