Skip to Content
SécuritéPartie 11 — Sécurité Moodle & LMS11.2 — sesskey, $DB & formulaires

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 $DB avec des paramètres ($DB->get_records_sql($sql, $params)), jamais de concaténation. XSS (Partie 7) : on échappe en sortie avec s() / format_string() / format_text() et on valide/nettoie les entrées avec clean_param() / PARAM_*. Utiliser ces API = sécurité par défaut ; les contourner (SQL concaténé, echo brut, action sans sesskey) = failles classiques des plugins.

🎯 Objectifs

  • Protéger les actions du CSRF avec sesskey (et via moodleform).
  • Requêter la base sans injection via l’API $DB paramé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 » (hors moodleform). Règle : toute action modifiant l’état vérifie le sesskey ; et on préfère moodleform qui le gère automatiquement. Ne jamais faire une mutation via un simple lien GET sans sesskey.

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) ou format_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 jamais echo une 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_param avec un PARAM_* strict pour toute entrée ; (2) $DB paramétré (ou helpers) pour la base, jamais de SQL concaténé ; (3) s()/format_string()/format_text() pour toute sortie, jamais d’echo brut ; plus require_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), un echo $data sans s()/format_text() (XSS stocké dans un champ vu par d’autres), une action sans require_sesskey() (CSRF), un optional_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_param avec le PARAM_* le plus strict, base via $DB paramétré et helpers (aucun SQL concaténé), sortie via s()/format_string()/format_text() avec le contexte (jamais d’echo brut), et require_sesskey() sur toutes les actions modifiant l’état (formulaires en moodleform qui l’intègrent). En revue, un plugin tiers qui utilise PARAM_RAW sans raison, concatène du SQL ou echo des 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 ?

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_* ?

À 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.

Last updated on