Chapitre 11.2 — sesskey, $DB & formulaires
⏱️ TL;DR — Moodle fournit des API sûres pour les trois grandes familles de failles. CSRF (Partie 8) : chaque action modifiant l’état doit vérifier le jeton
sesskey(require_sesskey()/confirm_sesskey()) ; les Moodle Forms (moodleform) l’intègrent automatiquement. Injection SQL (Partie 6) : on passe toujours par l’API$DBavec des paramètres ($DB->get_records_sql($sql, $params)), jamais de concaténation. XSS (Partie 7) : on échappe en sortie avecs()/format_string()/format_text()et on valide/nettoie les entrées avecclean_param()/PARAM_*. Utiliser ces API = sécurité par défaut ; les contourner (SQL concaténé,echobrut, action sanssesskey) = failles classiques des plugins.
🎯 Objectifs
- Protéger les actions du CSRF avec
sesskey(et viamoodleform). - Requêter la base sans injection via l’API
$DBparamétrée. - Échapper en sortie (
s,format_string,format_text) et valider les entrées (clean_param,PARAM_*). - Reconnaître les contournements dangereux dans du code de plugin.
CSRF : le jeton sesskey
Moodle protège les actions modifiant l’état par un jeton sesskey — sa version du jeton anti-CSRF (Partie 8). Chaque session a un sesskey ; on l’inclut dans les formulaires/liens d’action, et le code de traitement vérifie qu’il correspond avant d’agir.
// Verifier le sesskey avant toute action qui MODIFIE l'etat
require_sesskey(); // stoppe si le sesskey est absent/invalide (exception)
// ou, pour un test conditionnel :
if (!confirm_sesskey()) { print_error('invalidsesskey'); }Dans une URL d’action, on ajoute le sesskey :
$url = new moodle_url('/mod/xxx/action.php', ['id' => $id, 'sesskey' => sesskey()]);Les Moodle Forms font mieux : la classe moodleform intègre automatiquement la protection CSRF (jeton caché) et une bonne partie de la validation. Utiliser moodleform pour tes formulaires t’évite d’oublier le sesskey et te donne la validation côté serveur.
⚠️ Piège — Une action qui modifie l’état (supprimer, noter, changer un réglage) sans
require_sesskey()/confirm_sesskey()est un CSRF (Partie 8) : un site piégé peut faire exécuter l’action au nom d’un enseignant/admin connecté. C’est un oubli fréquent dans les plugins qui traitent une action « à la main » (horsmoodleform). Règle : toute action modifiant l’état vérifie lesesskey; et on préfèremoodleformqui le gère automatiquement. Ne jamais faire une mutation via un simple lien GET sanssesskey.
Injection SQL : l’API $DB
Moodle interdit en pratique le SQL brut concaténé : on passe par l’objet global $DB (une abstraction de base compatible multi-SGBD), avec des paramètres liés (rappel Partie 6). Les méthodes acceptent des placeholders et un tableau de paramètres.
global $DB;
// ✅ Placeholders nommes (:nom) + tableau de parametres
$users = $DB->get_records_sql(
"SELECT * FROM {user} WHERE institution = :inst AND deleted = 0",
['inst' => $institution]
);
// ✅ Helpers de haut niveau (encore plus surs, pas de SQL a ecrire)
$user = $DB->get_record('user', ['id' => $userid]); // WHERE id = ? (parametre)
$exists = $DB->record_exists('course', ['category' => $catid]);- Les accolades
{user}désignent la table avec le préfixe configuré (portabilité), et les valeurs passent en paramètres — pas de concaténation. - Les helpers (
get_record,get_records,insert_record,update_record,delete_records…) évitent d’écrire du SQL du tout : ils construisent des requêtes paramétrées à partir de tableaux de conditions.
// ❌ JAMAIS : SQL concatene avec une entree -> injection (Partie 6)
$DB->get_records_sql("SELECT * FROM {user} WHERE institution = '$institution'");Pour un tri/colonne dynamique (non paramétrable), même règle qu’en Partie 6 : allow-list, jamais l’entrée brute.
XSS : échapper en sortie, valider en entrée
Moodle fournit des fonctions d’échappement de sortie (Partie 7) et de validation d’entrée.
Échapper en sortie
// s() : echappe pour le contexte HTML (comme htmlspecialchars) — pour du TEXTE simple
echo s($nomUtilisateur);
// format_string() : pour des chaines courtes (noms, titres) — nettoie et applique les filtres
echo format_string($course->fullname);
// format_text() : pour du contenu riche (HTML autorise) — applique le nettoyage/les filtres Moodle
echo format_text($contenu, FORMAT_HTML, ['context' => $context]);- Pour du texte :
s()(échappement HTML) ouformat_string()(titres/noms). - Pour du HTML riche (contenu d’activité, descriptions) :
format_text()avec le contexte, qui applique le nettoyage et les filtres Moodle (l’équivalent de la sanitization, Partie 7). Ne jamaisechoune entrée brute.
Valider / nettoyer les entrées
Moodle impose de typer les paramètres reçus via optional_param()/required_param() avec un PARAM_*, qui nettoie la valeur selon le type attendu :
$id = required_param('id', PARAM_INT); // force un entier
$mode = optional_param('mode', 'view', PARAM_ALPHA); // lettres uniquement
$search = optional_param('q', '', PARAM_TEXT); // texte nettoye
// clean_param() pour nettoyer une valeur deja recuperee
$clean = clean_param($valeur, PARAM_INT);Le PARAM_* agit comme une validation/allow-list de format (Partie 6) : PARAM_INT garantit un entier, PARAM_ALPHA des lettres, etc. On choisit le type le plus strict correspondant à l’usage.
💡 Réflexe — Le trio Moodle à appliquer sur chaque page : (1)
required_param/optional_paramavec unPARAM_*strict pour toute entrée ; (2)$DBparamétré (ou helpers) pour la base, jamais de SQL concaténé ; (3)s()/format_string()/format_text()pour toute sortie, jamais d’echobrut ; plusrequire_sesskey()pour les mutations. Ces API sont la sécurité de Moodle : les utiliser = secure by default ; les contourner = les failles des Parties 6-8.
🎯 Côté attaquant — Dans un plugin Moodle, l’attaquant cherche les contournements de ces API : un
$DB->execute("... $param ...")concaténé (SQLi), unecho $datasanss()/format_text()(XSS stocké dans un champ vu par d’autres), une action sansrequire_sesskey()(CSRF), unoptional_param('x', '', PARAM_RAW)(validation désactivée) suivi d’un usage dangereux. Le cœur Moodle est solide ; les plugins qui n’utilisent pas les API sûres sont la porte d’entrée. Il teste aussi le XSS via les champs de profil/forum affichés à tous.
🧭 Sur FormaCampus — Les développements Moodle de FormaCampus utilisent exclusivement les API sûres : entrées via
required_param/optional_paramavec lePARAM_*le plus strict, base via$DBparamétré et helpers (aucun SQL concaténé), sortie vias()/format_string()/format_text()avec le contexte (jamais d’echobrut), etrequire_sesskey()sur toutes les actions modifiant l’état (formulaires enmoodleformqui l’intègrent). En revue, un plugin tiers qui utilisePARAM_RAWsans raison, concatène du SQL ouechodes données non échappées est refusé ou corrigé avant mise en production. C’est l’application, en langage Moodle, des parades des Parties 6, 7 et 8.
✏️ Exercices
Exercice 1 — Corrige le code de plugin. Repère les trois problèmes de sécurité.
$id = $_GET['id'];
$rows = $DB->get_records_sql("SELECT * FROM {feedback} WHERE course = $id");
echo $rows[0]->commentaire;
// (action de suppression declenchee plus bas par un lien GET sans sesskey)✅ Solution
(1) Entrée non validée + SQLi : $_GET['id'] est utilisé brut et concaténé dans le SQL. Correctif : $id = required_param('id', PARAM_INT); et requête paramétrée : $DB->get_records_sql("SELECT * FROM {feedback} WHERE course = :c", ['c' => $id]); (ou le helper get_records('feedback', ['course' => $id])). (2) XSS : echo $rows[0]->commentaire; affiche une donnée (potentiellement saisie par un utilisateur) sans échappement → stored XSS. Correctif : echo format_text($rows[0]->commentaire, FORMAT_HTML, ['context' => $context]); (ou s() si texte simple). (3) CSRF : une suppression via un lien GET sans sesskey est un CSRF (et viole la sémantique HTTP). Correctif : action en POST via moodleform ou lien avec sesskey, et require_sesskey() avant de supprimer. Bonus : il manque aussi require_login/require_capability (chapitre 11.1).
Exercice 2 — PARAM_RAW. Un dev récupère un paramètre avec optional_param('html', '', PARAM_RAW) et l’affiche avec echo. Pourquoi est-ce dangereux et que faire ?
✅ Solution
PARAM_RAW désactive le nettoyage : la valeur est prise telle quelle, sans validation ni filtrage. Combinée à un echo brut (sans échappement de sortie), c’est un XSS direct : l’utilisateur envoie <script>...</script> qui s’exécute. Que faire : (1) choisir le PARAM_* le plus strict correspondant au besoin (PARAM_INT, PARAM_ALPHANUM, PARAM_TEXT…) plutôt que PARAM_RAW ; (2) si du HTML riche est légitimement attendu, le récupérer avec précaution et l’afficher via format_text() (avec le contexte), qui applique le nettoyage Moodle — jamais un echo brut. PARAM_RAW ne s’utilise que dans des cas précis et toujours avec un échappement de sortie adapté.
🧠 Quiz de révision
1. Qu’est-ce que le sesskey et à quoi sert-il ?
sesskey et à quoi sert-il ?C’est le jeton anti-CSRF de Moodle (un par session). On l’inclut dans les actions modifiant l’état et on le vérifie avec require_sesskey()/confirm_sesskey() avant d’agir. Il empêche qu’un site tiers déclenche une action au nom d’un utilisateur connecté (CSRF, Partie 8). Les moodleform l’intègrent automatiquement.
2. Comment requête-t-on la base sans injection dans Moodle ?
Via l’API $DB avec des paramètres liés ($DB->get_records_sql($sql, $params) avec des placeholders :nom), ou les helpers (get_record, insert_record… à partir de tableaux de conditions). Jamais de SQL concaténé avec une entrée. Les {table} gèrent le préfixe.
3. Quelles fonctions Moodle échappent la sortie, et pour quel usage ?
s() (échappement HTML, pour du texte simple), format_string() (noms/titres courts), format_text() (contenu riche HTML, applique le nettoyage/les filtres avec le contexte). On les utilise pour toute sortie — jamais d’echo brut d’une donnée.
4. À quoi servent les PARAM_* ?
PARAM_* ?À valider/nettoyer les entrées selon un type : required_param('id', PARAM_INT), optional_param('mode', 'view', PARAM_ALPHA)… Le PARAM_* agit comme une allow-list de format (Partie 6). On choisit le type le plus strict ; PARAM_RAW (aucun nettoyage) est à éviter sauf cas précis avec échappement de sortie.
5. D’où viennent la plupart des failles SQLi/XSS/CSRF sur Moodle ?
Du code (surtout de plugins tiers) qui n’utilise pas les API sûres : SQL concaténé au lieu de $DB paramétré, echo brut au lieu de s()/format_text(), action sans require_sesskey(), PARAM_RAW injustifié. Le cœur Moodle est solide ; les contournements des API créent les failles.
Chapitre suivant : Plugins tiers & mises à jour — la surface d’attaque n°1 d’un Moodle : les plugins mal codés et les instances non mises à jour.