Skip to Content
WordPressPartie 7 — Développer des pluginsSécurité : nonces, sanitize, escape, capabilities

Chapitre 7.7 — Sécurité : nonces, sanitize, escape, capabilities

⏱️ TL;DR — La sécurité WordPress tient en quatre piliers à appliquer systématiquement : assainir en entrée (sanitize_* sur toute donnée reçue), échapper en sortie (esc_html/esc_attr/esc_url à l’affichage), vérifier les nonces (jeton anti-CSRF sur toute action mutante), vérifier les capabilities (current_user_can : cette personne a-t-elle le droit ?). Plus, pour la base : requêtes préparées ($wpdb->prepare, ch. 5.6). Oublier un seul de ces piliers = une faille (XSS, CSRF, injection SQL, élévation de privilèges). Ce chapitre est le plus important de la partie.

🎯 Objectifs

  • Appliquer les 4 piliers : sanitize / escape / nonce / capability.
  • Distinguer assainir (entrée) et échapper (sortie).
  • Choisir la bonne fonction selon le contexte.
  • Reconnaître et éviter les failles classiques (XSS, CSRF, IDOR, injection SQL).

1. Les quatre piliers (vue d’ensemble)

Un réflexe par frontière :

  1. Sanitize — quand une donnée entre (formulaire, URL, API).
  2. Escape — quand une donnée sort (affichage HTML).
  3. Nonce — quand une action change l’état (formulaire, lien d’action, AJAX).
  4. Capability — quand on autorise une action (l’utilisateur a-t-il le droit ?).
  5. Prepare — quand une donnée entre dans une requête SQL (ch. 5.6).

2. Assainir en entrée (sanitize)

Ne jamais faire confiance à une donnée reçue. On la nettoie selon son type attendu :

FonctionPour
sanitize_text_field()Texte simple (une ligne).
sanitize_textarea_field()Texte multiligne.
sanitize_email()E-mails.
absint() / (int)Entiers positifs.
sanitize_title()Slugs.
esc_url_raw()URLs à stocker.
wp_kses_post()HTML limité autorisé (contenu riche).
sanitize_key()Clés (minuscules, tirets).
$nom = sanitize_text_field( wp_unslash( $_POST['nom'] ?? '' ) ); $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); $age = absint( $_POST['age'] ?? 0 );

⚠️ Piège — oublier wp_unslash. WordPress ajoute des antislashes aux données de $_POST/$_GET (magie historique). Appliquez wp_unslash() avant d’assainir, sinon vous stockez des \" parasites.


3. Échapper en sortie (escape)

Même une donnée « propre » en base doit être échappée à l’affichage, selon le contexte HTML :

FonctionContexte
esc_html()Texte entre balises.
esc_attr()Valeur d’attribut (value="…", title="…").
esc_url()URL dans href/src.
esc_textarea()Contenu d’un <textarea>.
wp_kses_post()HTML riche autorisé (contenu de post).
esc_js()Chaîne dans du JS inline (rare ; préférez localize).
echo '<a href="' . esc_url( $url ) . '" title="' . esc_attr( $titre ) . '">' . esc_html( $texte ) . '</a>';

⚠️ Piège critique — la faille XSS. Afficher une donnée sans l’échapper (surtout venue de l’utilisateur : commentaire, champ meta, paramètre d’URL) permet d’injecter du <script>XSS. Règle absolue : on échappe au moment où l’on affiche, dans le bon contexte. « Assaini en entrée » ne dispense pas d’« échapper en sortie » (les deux sont nécessaires).

💡 Pour un dev React — En React, le JSX échappe le texte par défaut (et dangerouslySetInnerHTML est le mot-clé qui vous alerte). En PHP/WordPress, rien n’échappe pour vous : c’est votre responsabilité, à chaque echo. Voyez esc_html() comme le comportement par défaut de JSX que vous devez écrire à la main, et wp_kses_post() comme un dangerouslySetInnerHTML filtré (liste blanche de balises). Le réflexe « échapper à la sortie » remplace la sécurité automatique de React.


4. Les nonces (anti-CSRF)

Un nonce (number used once) est un jeton lié à l’utilisateur et à une action, qui prouve que la requête vient bien de votre formulaire/lien (et pas d’un site tiers malveillant — CSRF).

// Émettre le nonce (dans un formulaire) wp_nonce_field( 'wpr_action_supprimer', 'wpr_nonce' ); // Dans un lien d'action $url = wp_nonce_url( admin_url( 'admin-post.php?action=wpr_delete&id=42' ), 'wpr_action_supprimer' ); // Vérifier à la réception (AVANT toute mutation) if ( ! isset( $_POST['wpr_nonce'] ) || ! wp_verify_nonce( $_POST['wpr_nonce'], 'wpr_action_supprimer' ) ) { wp_die( 'Action non autorisée.' ); }

⚠️ Piège — action mutante sans nonce. Toute action qui modifie l’état (créer, supprimer, mettre à jour) doit vérifier un nonce. Sans cela, un attaquant peut faire exécuter l’action à un admin connecté via une page piégée (CSRF). Le nonce ne remplace pas la capability (§5) : les deux sont requis.


5. Les capabilities (autorisation)

Vérifier le droit : cette personne a-t-elle le droit de faire cette action, sur cet objet ? (Rappel ch. 2.2 : on teste une capability, jamais un rôle.)

if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( 'Droits insuffisants.' ); }

⚠️ Piège — l’IDOR (référence directe non contrôlée). Vérifier current_user_can('edit_posts') (droit générique d’éditer des articles) ne suffit pas si l’utilisateur édite l’article d’un autre. Utilisez la forme avec l’objet : current_user_can('edit_post', $post_id) → « peut-il éditer CE post ? ». Autorisation génériqueappartenance. C’est une faille très fréquente (accès aux données d’autrui).


6. Le patron complet d’une action sécurisée

Nonce → capability → sanitize → agir → (escape à l’affichage) :

add_action( 'admin_post_wpr_delete_livre', 'wpr_delete_livre' ); function wpr_delete_livre() { // 1) NONCE check_admin_referer( 'wpr_action_supprimer' ); // vérifie le nonce ou wp_die // 2) SANITIZE des entrées $id = absint( $_GET['id'] ?? 0 ); // 3) CAPABILITY sur l'objet if ( ! $id || ! current_user_can( 'delete_post', $id ) ) { wp_die( 'Non autorisé.' ); } // 4) Agir wp_trash_post( $id ); // 5) Rediriger proprement wp_safe_redirect( admin_url( 'edit.php?post_type=livre' ) ); exit; }

check_admin_referer() / check_ajax_referer() combinent vérification de nonce et arrêt en cas d’échec — pratiques.


7. Autres réflexes

  • SQL : $wpdb->prepare() pour toute valeur (ch. 5.6). Jamais de concaténation.
  • Uploads/fichiers : valider le type, utiliser les API WordPress (wp_handle_upload, wp_check_filetype).
  • Redirections : wp_safe_redirect() (limite aux hôtes autorisés) plutôt que wp_redirect() avec une URL utilisateur.
  • Secrets : jamais côté client (ch. 6.4) ni en dur dans le code versionné (ch. 4.2).
  • REST/AJAX : vérifier permission_callback (Partie 9) et nonce (wp_rest).

✏️ Exercices

  1. Un plugin affiche echo $_GET['q']; comme titre de résultats de recherche. Quelle faille, et comment corriger ?
  2. Différence entre current_user_can('edit_posts') et current_user_can('edit_post', $id) ? Pourquoi le second est parfois indispensable ?
  3. Donnez l’ordre correct des vérifications pour une action de suppression déclenchée par un lien d’admin.

✅ Solution

  1. XSS (la valeur brute de l’URL est affichée → un <script> s’exécute). Correction : échapper à la sortieecho esc_html( sanitize_text_field( wp_unslash( $_GET['q'] ?? '' ) ) ); (assainir en entrée et échapper en sortie).
  2. edit_posts = droit générique d’éditer des articles ; edit_post, $id = droit d’éditer CET article précis (tient compte de l’appartenance/statut). Le second évite l’IDOR : un auteur ne doit pas éditer l’article d’un autre même s’il a edit_posts.
  3. Nonce (check_admin_referer) → sanitize de l’id (absint) → capability sur l’objet (current_user_can('delete_post', $id)) → agir (wp_trash_post) → redirection sûre (wp_safe_redirect).

🧠 Quiz de révision

1. Quels sont les quatre piliers de sécurité ?

Sanitize (entrée), Escape (sortie), Nonce (anti-CSRF sur les actions), Capability (autorisation).

2. Assainir en entrée dispense-t-il d’échapper en sortie ?

Non : les deux sont nécessaires. On assainit ce qui entre et on échappe au moment d’afficher (contre le XSS).

3. À quoi sert un nonce ?

À prouver qu’une action mutante vient bien de votre formulaire/lien (protection CSRF).

4. Qu’est-ce que l’IDOR et comment l’éviter ?

Accéder à l’objet d’autrui via une capability générique ; on l’évite en testant la capability avec l’objet (current_user_can('edit_post', $id)).

5. Comment sécuriser une valeur dans une requête SQL ?

Avec $wpdb->prepare() (requête préparée) — jamais de concaténation.


Chapitre suivant : Internationalisation (i18n).