Les compromissions de sites WordPress passent rarement par le cœur, très bien tenu, et presque toujours par une extension. Sur les extensions écrites sur mesure pour un projet, le taux de défauts est nettement plus élevé que sur celles publiées, faute de relecture et de mise à jour. Sécuriser une extension WordPress repose pourtant sur trois contrôles simples, toujours les mêmes, qu'il suffit d'appliquer systématiquement : vérifier que la requête vient bien du site, vérifier que l'utilisateur a le droit de faire ce qu'il demande, et traiter toute donnée à l'entrée comme à la sortie. Aucun de ces trois contrôles n'est difficile, et c'est leur application partielle qui produit les failles.

Les trois piliers et ce qu'ils couvrent

Chacun de ces contrôles répond à une menace distincte, et aucun ne remplace les deux autres. Les confondre conduit à des extensions où la présence visible d'un mécanisme de sécurité masque l'absence des deux autres. Ces principes recoupent ceux exposés dans notre article sur la manière de créer un shortcode moderne et sécurisé.

Le nonce répond à l'origine

Le nonce vérifie que la requête a bien été émise depuis une page servie par votre site, et non depuis un site tiers exploitant la session ouverte du visiteur. Il ne dit rien de l'identité de la personne ni de ses droits. Une extension qui vérifie le nonce et rien d'autre laisse n'importe quel utilisateur connecté, même avec le rôle le plus limité, exécuter n'importe quelle action de l'administration. C'est probablement la confusion la plus répandue et la plus lourde de conséquences sur les sites à plusieurs contributeurs. Un abonné, un client de boutique ou un auteur peut alors atteindre des fonctions qui ne lui sont pas destinées, simplement en envoyant la bonne requête.

La capacité répond aux droits

La vérification de capacité contrôle que l'utilisateur courant a le droit d'effectuer l'action demandée. Elle ne dit rien de l'origine de la requête. Une extension qui vérifie les capacités sans nonce reste vulnérable à la falsification de requête : un administrateur consultant une page piégée déclenchera l'action avec ses propres droits, ce qui est exactement le scénario le plus dommageable. Ces deux contrôles se complètent et doivent toujours être présents ensemble. Les écrire l'un après l'autre, dans cet ordre, en tête de chaque traitement, est une habitude qui coûte deux lignes et supprime la moitié des défauts constatés.

L'échappement répond au contenu

Le traitement des données à la sortie garantit qu'aucune valeur affichée ne sera interprétée comme du code par le navigateur. Il est indépendant des deux autres, puisqu'une donnée parfaitement légitime, saisie par un utilisateur autorisé depuis une page authentique, peut contenir des caractères dangereux à l'affichage. Cette indépendance explique pourquoi l'échappement doit être appliqué systématiquement et non réservé aux données jugées suspectes, raisonnement qui laisse toujours passer quelque chose. Une valeur enregistrée il y a trois ans par un compte depuis supprimé reste tout aussi dangereuse à l'affichage qu'une valeur reçue à l'instant.

Ce que le cœur fait déjà

WordPress fournit tout l'outillage nécessaire : génération et vérification de nonces, système de rôles et de capacités, fonctions d'échappement par contexte, fonctions de nettoyage, couche d'accès à la base avec préparation de requêtes. Rien de tout cela n'est à écrire, il suffit de l'employer. Les défauts constatés ne viennent presque jamais d'une absence d'outil mais d'un oubli d'appel, ce qui rend le sujet plus une affaire de discipline que de compétence technique avancée. Une liste de contrôle de six lignes, relue avant chaque livraison, suffit à atteindre un niveau très correct.

Où les oublis se produisent

Les trois contrôles sont généralement présents sur le chemin principal, celui que le développeur a écrit en premier et testé. Ils manquent sur les chemins ajoutés ensuite : un appel asynchrone ajouté pour améliorer l'ergonomie, une action de suppression rapide, un export, un point d'entrée destiné à un usage interne. Ce sont exactement ces chemins secondaires qui constituent la porte d'entrée des compromissions, précisément parce qu'ils ont échappé à l'attention. Le recensement de tous les points d'entrée de l'extension, tenu à jour, est donc le document le plus utile pour éviter ce genre d'oubli.

Vérification des droits et des données dans une extension sur mesure

Les nonces WordPress

Le mécanisme est simple d'emploi et comporte quelques subtilités qui expliquent les défauts observés. Le principe général est le même que celui décrit dans notre article sur les jetons CSRF et leur vérification.

Générer avec une action nommée

Un nonce est créé pour une action précise, désignée par une chaîne. Cette chaîne doit être spécifique et inclure idéalement l'identifiant de l'objet concerné, afin qu'un nonce valide pour la suppression d'un élément ne soit pas valide pour un autre. Utiliser une chaîne générique pour toute l'extension divise l'intérêt du mécanisme par autant d'actions qu'elle en comporte, et c'est un raccourci qu'on prend facilement sans en mesurer la conséquence. La convention la plus simple consiste à composer la chaîne à partir du nom de l'extension, du nom de l'action et de l'identifiant concerné.

Le poser dans le formulaire

Une fonction dédiée écrit directement le champ caché dans le formulaire, ce qui évite d'avoir à le construire soi même. Elle ajoute par défaut un second champ contenant l'adresse de référence, utile pour la redirection après traitement. Il n'y a aucune raison de ne pas l'utiliser, et son emploi rend la présence du contrôle visible en relecture, ce qui facilite considérablement les revues de code sur des extensions volumineuses. Sur les listes affichant une action par ligne, cette fonction doit être appelée pour chaque ligne, avec l'identifiant correspondant.

Vérifier du bon côté

La vérification doit intervenir en tout premier dans le traitement, avant la lecture des autres champs. Deux fonctions existent : l'une retourne un résultat que l'on doit traiter, l'autre interrompt l'exécution en cas d'échec. La seconde est préférable pour les traitements d'administration, puisqu'elle ne laisse aucune possibilité d'oublier de traiter le résultat, oubli qui transforme la vérification en simple décoration. Ce cas se rencontre régulièrement : la fonction est appelée, son résultat n'est jamais testé, et le traitement se poursuit quel que soit le verdict.

Le cas des appels asynchrones

Les appels effectués depuis le navigateur doivent transmettre le nonce, généralement injecté dans la page au moment du chargement du script. Côté serveur, une fonction de vérification adaptée à ce contexte existe et retourne une réponse d'erreur propre. C'est le point d'oubli le plus fréquent de tous, car ces appels sont souvent ajoutés après coup, dans une logique d'amélioration de l'interface plutôt que de traitement de données. Le fait que l'appel ne soit pas visible dans l'adresse renforce l'impression trompeuse qu'il serait moins exposé.

La durée de validité

Un nonce WordPress reste valide environ vingt quatre heures, avec un renouvellement à mi parcours. Cette durée est généralement suffisante, mais elle produit des rejets sur les formulaires laissés ouverts très longtemps, situation qui se rencontre dans les administrations où une page reste ouverte plusieurs jours. Le message d'erreur par défaut n'est pas très explicite, et il vaut mieux le remplacer par une invitation claire à recharger la page. Un rafraîchissement automatique du nonce par un appel périodique règle également le problème sur les interfaces prévues pour rester ouvertes.

Ce qu'un nonce ne fait pas

Il ne remplace pas la vérification des droits, ne valide pas les données reçues et ne protège pas contre les injections. Il est également connu de l'utilisateur, puisqu'il figure dans la page, ce qui signifie qu'il ne constitue en rien un secret partagé permettant d'authentifier quelqu'un. Cette précision paraît évidente et se révèle utile devant les implémentations qui l'emploient comme un identifiant de session bis. Un nonce ne doit jamais servir à autoriser l'accès à une ressource, uniquement à confirmer l'origine d'une action.

Contexte Nonce Capacité Traitement des données
Formulaire d'administration Champ dédié Selon l'action Entrée et sortie
Appel asynchrone administrateur En-tête ou paramètre Selon l'action Entrée et sortie
Appel asynchrone public Nonce public Vérification métier Entrée et sortie
Point d'entrée de programmation Selon authentification Contrôle de permission Entrée et sortie
Affichage d'un contenu Sans objet Selon visibilité Sortie uniquement
Tâche planifiée Sans objet Sans objet Entrée et sortie

Les capacités et les rôles

La gestion des droits est le contrôle le plus souvent bâclé, généralement par une vérification approximative qui donne l'illusion d'une protection.

Vérifier une capacité, pas un rôle

Le contrôle doit porter sur une capacité, non sur le nom d'un rôle. Un site peut définir des rôles sur mesure, et une extension de gestion des droits peut attribuer une capacité à un rôle qui ne l'avait pas. Vérifier qu'un utilisateur porte le rôle d'administrateur, plutôt qu'il dispose de la capacité correspondant à l'action, casse toute personnalisation et produit des extensions qui refusent de fonctionner sur les configurations un peu élaborées. La liste des capacités du cœur est documentée et couvre la quasi totalité des besoins courants.

Choisir la bonne capacité

La capacité vérifiée doit correspondre à ce que l'action fait réellement. Une page de réglages généraux appelle la capacité de gestion des options, une action sur un contenu appelle la capacité d'édition de ce contenu précis, une action sur les utilisateurs appelle la capacité correspondante. Vérifier systématiquement la capacité la plus élevée est un excès qui empêche les usages légitimes, et vérifier la plus basse est une faille. Le choix demande de regarder la liste des capacités disponibles, exercice de cinq minutes. Ce choix mérite d'être commenté dans le code, la raison ayant tendance à s'oublier.

Le contrôle par objet

Les capacités portant sur des contenus acceptent un identifiant d'objet, ce qui permet de vérifier que l'utilisateur peut modifier ce contenu précis et non n'importe lequel. Cette forme du contrôle est indispensable dès que le site comporte plusieurs contributeurs, faute de quoi chacun peut agir sur les contenus des autres. C'est un défaut fréquent et difficile à repérer, puisque le contrôle existe et paraît correct au premier regard. Il se repère en cherchant les appels de vérification auxquels aucun identifiant n'est passé alors que l'action porte sur un objet précis.

Vérifier au bon endroit

La vérification doit se faire dans le traitement, pas seulement dans l'affichage. Masquer un bouton à un utilisateur qui n'a pas le droit ne l'empêche pas d'envoyer la requête directement. Ce défaut se rencontre régulièrement sur les extensions dont l'interface est soignée : les éléments sont bien conditionnés à l'affichage, et le traitement accepte n'importe quelle requête. La règle est de conditionner les deux, sans exception. L'affichage sert le confort de l'utilisateur, le traitement sert la sécurité, et confondre les deux rôles est à l'origine de nombreuses failles.

Les points d'entrée publics

Une extension proposant une action accessible aux visiteurs non connectés doit être particulièrement prudente. Il n'y a pas de capacité à vérifier, ce qui déplace toute la protection vers la validation des données et vers des contrôles métier explicites : limiter le nombre de requêtes, vérifier la cohérence des données, ne jamais faire confiance à un identifiant reçu. Ces points d'entrée méritent une attention proportionnelle à leur exposition. Une limitation du nombre d'appels par adresse et par minute constitue une protection élémentaire et souvent suffisante contre les usages abusifs.

Déclarer ses propres capacités

Une extension apportant des fonctionnalités métier gagne à déclarer ses propres capacités plutôt qu'à réutiliser celles du cœur. Cela permet à l'administrateur du site d'attribuer finement les droits sans donner accès à tout le reste. Cette pratique demande quelques lignes supplémentaires et transforme une extension rigide en outil réellement adaptable, ce que les clients apprécient dès que l'organisation compte plusieurs profils d'utilisateurs. Ces capacités doivent être attribuées aux rôles à l'activation de l'extension et retirées proprement à sa désinstallation.

Défauts de sécurité relevés sur des extensions WordPress sur mesure
Vérification de capacité absente ou trop faible
32 %
Nonce absent sur un appel asynchrone
26 %
Sortie non échappée
21 %
Contrôle présent à l'affichage seulement
14 %
Requête construite par concaténation
7 %

Défauts constatés lors de revues d'extensions développées pour un projet. Le dernier, le plus rare, est aussi celui dont les conséquences sont les plus étendues.

Entrées, sorties et le reste

Le troisième pilier couvre tout ce qui touche aux données, de leur réception à leur affichage, en passant par leur stockage.

Nettoyer à l'entrée

Les données reçues doivent être ramenées au format attendu avant tout stockage : un entier converti en entier, une adresse électronique validée, un texte débarrassé des balises non souhaitées. WordPress fournit une fonction de nettoyage par type de donnée, et leur emploi doit être systématique. Ce nettoyage ne remplace pas l'échappement à la sortie, les deux opérations répondant à des questions différentes, et cette distinction reste la plus mal comprise du sujet. Nettoyer sert à obtenir une donnée propre en base, échapper sert à obtenir un affichage sûr, et les deux sont nécessaires.

Préparer les requêtes

Toute requête construite avec une valeur venant de l'extérieur doit passer par la fonction de préparation, sans exception et quel que soit le nettoyage déjà appliqué. La concaténation directe dans une requête reste la faille la plus grave qu'une extension puisse comporter, puisqu'elle donne accès à l'ensemble de la base. Cette règle ne souffre aucune exception, y compris pour les valeurs qu'on croit maîtriser, comme un identifiant issu d'une liste déroulante. Une valeur peut toujours être remplacée par autre chose au moment de l'envoi de la requête, quel que soit le contrôle appliqué dans le formulaire.

Échapper à la sortie

Chaque valeur affichée doit traverser la fonction d'échappement correspondant à son contexte : corps du document, attribut, adresse, contexte de script. L'emploi systématique de ces fonctions, y compris sur des valeurs provenant de la configuration de l'extension, coûte quelques caractères et supprime définitivement une catégorie entière de vulnérabilités. C'est le contrôle le plus facile à vérifier en relecture, puisqu'il est visible ligne par ligne.

Protéger les fichiers

Les fichiers de l'extension ne doivent pas être exécutables directement, ce qui s'obtient par un contrôle en tête de chaque fichier vérifiant que le contexte WordPress est bien chargé. Sans cela, un accès direct au fichier peut produire des erreurs révélant des chemins, voire exécuter du code hors contexte. Cette précaution tient en une ligne par fichier et figure dans tous les guides, ce qui n'empêche pas de la trouver absente régulièrement. Elle protège notamment des messages d'erreur du langage, qui révèlent volontiers l'arborescence complète du serveur.

Traiter les téléversements

Une extension acceptant des fichiers doit employer la fonction de traitement fournie par le cœur, qui applique les contrôles de type, renomme les fichiers et les place dans le répertoire prévu. Écrire ce traitement soi même expose à plusieurs vulnérabilités classiques, dont le dépôt d'un fichier exécutable dans un répertoire accessible. C'est le point où l'écart entre une implémentation maison et l'emploi du cœur est le plus dangereux.

Surveiller après la mise en ligne

Une extension sur mesure ne bénéficie d'aucune surveillance externe, contrairement à celles publiées dont les failles sont signalées publiquement. Un contrôle périodique du code, complété par une solution de surveillance du site, compense partiellement cette absence. Les dispositifs disponibles sont présentés dans notre article sur la manière de protéger un site WordPress avec Wordfence, et ils constituent un filet utile plutôt qu'une dispense de faire les choses correctement. Ce filet ne dispense en aucun cas des vérifications de capacité, qui restent la seule protection réellement fiable.