Protection CSRF (Origin + jeton scopé)
Pourquoi protéger les POST
Sans jeton CSRF lié à la session, un site tiers peut soumettre un formulaire POST vers PmaControl pendant qu'un utilisateur authentifié navigue ailleurs. Le navigateur joint automatiquement les cookies de session et l'action s'exécute hors intention utilisateur — modification de configuration, lancement d'opération, mise à jour inline.
Helper mutualisé Glial
À partir de Glial v5.1.40, la logique CSRF et la validation Origin/Referer sont mutualisées dans le framework, donc partagées par tous les contrôleurs PmaControl :
Glial\Security\Csrf::issueToken()/Csrf::validateToken()— émission et validation d'un jeton par scope fonctionnel.Glial\Http\Request::isSameSite()— comparaison Origin (priorité) puis Referer (fallback) contre l'origine courante.
Worker.php contient uniquement l'orchestration HTTP propre à POST /Worker/update.Schéma du flux
Émission du jeton
Avant de rendre une vue contenant un formulaire ou une cellule inline-editable, le contrôleur émet un jeton scopé par fonctionnalité :
<?php
use Glial\Security\Csrf;
$token = Csrf::issueToken($_SESSION, 'worker.update');
$this->set('csrf_token', $token);
worker.update soit rejouable sur daemon.start. Le jeton est stocké dans $_SESSION['csrf_tokens'][$scope] et persiste jusqu'à la fin de la session utilisateur.Injection côté client
Pour les cellules inline-editable (bootstrap-editable), le jeton est injecté via deux attributs 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') pour neutraliser toute injection HTML/JS dans la cellule.Le JS App/Webroot/js/Tree/index.js détecte la présence de data-csrf-token et ajoute automatiquement le jeton aux paramètres POST envoyés par 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) est rappelé sur le contexte rechargé pour relier le nouveau jeton aux nouvelles cellules.Chaîne de validation côté serveur
Quatre contrôles s'enchaînent dans cet ordre exact, en amont de toute construction SQL :
- Méthode HTTP — uniquement
POST; toute autre méthode renvoie405 Method Not Allowed. - Origine same-site —
Originen priorité, puisRefereren fallback ; les valeursnull, protocol-relatives ou avec scheme/host/port différent sont rejetées avec403 Invalid request origin. - Jeton CSRF — comparaison
hash_equals()entre le champcsrf_tokendu payload et$_SESSION['csrf_tokens'][$scope]; un jeton manquant ou invalide retourne403 Invalid CSRF token. - Allowlist payload — uniquement les champs et types prévus pour cet endpoint ; un payload hors liste retourne
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
}
}
Référence — codes de retour
| Code | Cause | Quand |
|---|---|---|
405 |
Method Not Allowed | Méthode HTTP autre que POST. |
403 |
Invalid request origin | Origin/Referer hors site, Origin: null, protocol-relative ou scheme/host/port différent. |
403 |
Invalid CSRF token | Jeton absent du payload ou ne correspondant pas à $_SESSION['csrf_tokens'][$scope]. |
400 |
Invalid worker update payload | Champ hors allowlist, valeur non entière, pk négatif ou nul. |
Exemptions documentées
Le helper CSRF couvre uniquement les POST web. Les surfaces suivantes sont explicitement exemptées :
- CLI
php glial …— pas de cookie de session, pas de surface CSRF. - API REST machine-to-machine — authentification par jeton API séparé ; le contrôle Origin/Referer est désactivé pour ces routes et l'exemption est documentée par endpoint.
- Endpoints read-only — convertis en GET, donc hors du périmètre CSRF.
Tests dédiés
La protection CSRF est couverte par des tests PHPUnit qui matérialisent explicitement le scénario d'attaque historique :
./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 ou un autre site externe, le payload est rejeté en 403 Invalid request origin avant toute écriture, même si un jeton CSRF valide est fourni — la chaîne de validation refuse la requête sur le contrôle Origin avant d'arriver au jeton.