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 :
- Sanitize — quand une donnée entre (formulaire, URL, API).
- Escape — quand une donnée sort (affichage HTML).
- Nonce — quand une action change l’état (formulaire, lien d’action, AJAX).
- Capability — quand on autorise une action (l’utilisateur a-t-il le droit ?).
- 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 :
| Fonction | Pour |
|---|---|
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). Appliquezwp_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 :
| Fonction | Contexte |
|---|---|
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
dangerouslySetInnerHTMLest le mot-clé qui vous alerte). En PHP/WordPress, rien n’échappe pour vous : c’est votre responsabilité, à chaqueecho. Voyezesc_html()comme le comportement par défaut de JSX que vous devez écrire à la main, etwp_kses_post()comme undangerouslySetInnerHTMLfiltré (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érique ≠ appartenance. 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 quewp_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
- Un plugin affiche
echo $_GET['q'];comme titre de résultats de recherche. Quelle faille, et comment corriger ? - Différence entre
current_user_can('edit_posts')etcurrent_user_can('edit_post', $id)? Pourquoi le second est parfois indispensable ? - Donnez l’ordre correct des vérifications pour une action de suppression déclenchée par un lien d’admin.
✅ Solution
- XSS (la valeur brute de l’URL est affichée → un
<script>s’exécute). Correction : échapper à la sortie —echo esc_html( sanitize_text_field( wp_unslash( $_GET['q'] ?? '' ) ) );(assainir en entrée et échapper en sortie). 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 aedit_posts.- 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).