J’ai vu des sites WordPress tenir debout pendant des mois, puis basculer en une nuit. Pas forcément à cause d’une “faille énorme” médiatisée, mais à cause d’un angle mort classique: un bout de code ajouté pour “juste un petit truc”, un hook mal borné, un shortcode qui accepte trop de paramètres, ou une exécution qui devient dangereuse dès qu’un contexte change.
Quand on parle de sécurisation WordPress, on pense souvent aux mots de passe, au durcissement du serveur ou au filtrage côté navigateur. Tout cela compte. Mais une grande partie du risque se joue dans WordPress lui-même, au niveau des points d’extension: hooks, shortcodes et actions qui peuvent conduire à l’exécution de code, à l’inclusion de fichiers, ou à des comportements inattendus quand des données entrent dans l’application.
L’objectif ici est d’expliquer comment mener un audit concret, sans tomber dans la paranoïa, et comment décider quoi corriger en premier.
Comprendre la surface d’attaque “programmation”
WordPress est conçu pour être étendu. Cette qualité devient un problème quand l’extension n’est pas cadrée. Les hooks (actions et filtres) permettent de brancher du code à des moments précis du cycle de vie. Les shortcodes rendent l’interface souple, parce qu’ils transforment du texte en sorties plus riches. Et certaines fonctions et patterns, s’ils sont utilisés sans garde-fous, peuvent ouvrir la porte à des chemins dangereux.
Dans un audit, la question n’est pas seulement “est-ce que la fonction existe”. La vraie question est plutôt:
- quelles entrées peuvent atteindre ce code, quelle est la forme attendue des paramètres, à quel moment le code s’exécute, et quel est le niveau de privilège requis.
C’est là qu’on découvre des vulnérabilités réelles, souvent déclenchées par un utilisateur qui ne devrait pas pouvoir faire grand chose, ou par un plugin tiers qui pensait ne s’adresser qu’à l’administrateur.
Hooks: l’endroit où les entrées se transforment en effets
Un hook est une promesse: “quand WordPress arrive ici, je vais exécuter ma fonction”. C’est pratique, mais ça impose de comprendre ce que déclenche chaque moment et ce que transportent les paramètres.
Les patterns à traquer
Dans un audit de hooks, je commence rarement par “chercher eval” ou “chercher exec”. Ces mots sont parfois présents, mais ce n’est pas le seul indicateur. Le risque le plus fréquent vient de combinaisons moins évidentes.
Regardez ces catégories:
1) Hooks qui traitent du contenu ou des données de requête
Par exemple, un filtre appliqué au contenu avant rendu, ou une action déclenchée sur init, wp_loaded, template_redirect. Si une variable provenant de $_GET ou $_POST peut atteindre ce chemin sans validation stricte, le terrain est prêt.2) Hooks qui modifient des templates ou incluent des fichiers
Un hook peut décider d’inclure un fichier selon un paramètre, même indirectement. Si la source de ce paramètre n’est pas maîtrisée, vous pouvez obtenir une inclusion de fichiers non prévue, ou des comportements d’exécution imprévus.3) Hooks “admin” qui se connectent à des capacités
Beaucoup de code “semble admin-only”, mais en pratique il est possible de rater un contrôle, surtout si le contrôle se fait trop tard. Vérifiez systématiquement les checks de capacité au début du handler, pas après des opérations.4) Hooks qui modifient des requêtes ou des formulaires
Si un hook altère une requête SQL via des morceaux construits à la main, le risque devient injection. Même sans injection SQL, un filtrage insuffisant peut conduire à un XSS stocké, ou à une redirection mal contrôlée.Un cas fréquent: le hook de contenu trop permissif
Le scénario que je rencontre le plus souvent ressemble à ceci: un plugin ajoute un filtre de type the_content ou un équivalent, puis “customise” des tags, par exemple pour gérer un syntaxe maison ou des shortcodes implicites. Le code parse une chaîne, puis réinjecte du HTML, parfois en faisant des remplacements sur des morceaux.
Le problème survient quand le code:
- ne normalise pas correctement les entrées, ne limite pas ce qui peut être rendu, ou ajoute des attributs HTML à partir de paramètres non filtrés.
Ce n’est pas toujours une “faille RCE”. Parfois c’est “juste” un XSS puissant. Mais la plupart des intrusions passent par un XSS d’abord, puis pivotent vers d’autres actions, surtout si la session admin est accessible via des tokens ou si l’attaquant peut modifier des options.
Vérifier le moment d’exécution
Un autre point concret: un même code peut être sûr ou dangereux selon le moment où il tourne.
Un handler accroché sur un hook très précoce peut modifier l’environnement avant que la couche de sécurité ne soit en place. Un handler branché sur un hook plus tard peut au contraire bénéficier de filtrages déjà appliqués.
Dans l’audit, je note les hooks utilisés, puis je regarde si le code:
- s’exécute à chaque page ou seulement dans un contexte prévu, traite des entrées non échappées, et applique des garde-fous avant toute opération.
La “sécurité” n’est pas un sentiment, c’est un enchaînement.
Shortcodes: le compromis entre flexibilité et contrôle
Les shortcodes sont souvent sous-estimés dans les audits. On les voit comme une fonction d’interface. Pourtant, un shortcode est un mini-langage: il lit des attributs, il produit du contenu. Et si les attributs ne sont pas filtrés, on peut obtenir une injection HTML, une injection de scripts, ou des requêtes déclenchées à la demande.
Comment un shortcode devient dangereux
Deux chemins reviennent souvent.
Le premier est classique: le shortcode accepte un paramètre qui “cible” une ressource, par exemple un identifiant, un nom de fichier, une URL, ou un contenu à inclure. Si le code se contente de construire un chemin ou une requête à partir du paramètre, le contrôle devient fragile. Sans validation forte, un attaquant peut forcer le code à sortir du domaine prévu.
Le second est plus subtil: le shortcode déclenche une logique qui n’est pas censée être exposée au contenu. Par exemple, un shortcode qui “résout” un template, qui applique une transformation complexe, ou qui appelle une fonction qui n’est pas pensée pour tourner sur un contenu non fiable.
L’exemple mental que j’utilise en audit
Quand je lis un shortcode, je me pose une série de questions dans ma tête, comme si j’étais un plugin malicieux:
- Quels attributs sont acceptés, et quels sont leurs défauts ? Est-ce que le shortcode suppose que l’utilisateur a déjà validé l’entrée ? L’attribut est-il échappé à la sortie, ou seulement encodé à l’intérieur ? Est-ce que le shortcode exécute une requête basée sur un paramètre ? Le shortcode est-il limité à certains rôles, ou seulement à un mode (éditeur, administrateur) ?
Un détail qui fait gagner du temps: WordPress a déjà des helpers pour nettoyer des attributs, pour échapper la sortie et pour contrôler les capacités. Le vrai “signal” dans le code, c’est l’usage cohérent de ces helpers autour des points de danger.
Une erreur fréquente: “on filtre après”
Je l’ai vu maintes fois: le shortcode fait quelque chose avec un paramètre, puis “on nettoie” ce qui va à l’écran. Nettoyer la sortie ne suffit pas si le paramètre a déjà servi à un appel sensible, comme une inclusion, une requête ou un appel à une fonction qui dépend de l’entrée.
Dans un audit, je traite la séquence comme un scénario d’attaque. Dès qu’un paramètre non fiable touche un endroit sensible, c’est là que la correction doit commencer.
Exécutions dangereuses: détecter sans s’aveugler
Par “exécution dangereuse”, j’entends des comportements où du code ou une logique imprévue peut être déclenchée à partir d’entrées contrôlables. Cela inclut l’exécution de commandes, l’évaluation de chaînes, des inclusions de fichiers non maîtrisées, et parfois des chemins d’authentification contournables qui mènent à des actions critiques.
Les signatures à rechercher dans le code
Je ne fais pas une chasse au mot. Je fais une chasse au comportement.
Cela dit, certains termes et familles de fonctions doivent attirer l’attention. Je les traite comme des drapeaux jaunes. S’il y en a, je regarde le contexte exact:
- Est-ce que l’entrée vient de $_GET, $_POST, d’un shortcode, ou d’un champ en base ? Est-ce que l’entrée est validée avec une liste blanche ? Est-ce que le code limite le scope, par exemple l’URL à un domaine connu, ou le nom de fichier à un répertoire précis ? Est-ce que le résultat est échappé et encadré ?
Les pièges classiques sont les patterns où une chaîne est concaténée pour former une commande, un chemin, ou un fragment d’exécution.
La réalité terrain: la dangereuse concaténation
Le cas le plus “banal” et pourtant destructeur que j’ai rencontré: une fonction utilitaire ajoutée par un développeur pour “debugger”, restée en production. Elle était protégée “en théorie” par un paramètre, mais ce paramètre pouvait être contourné via un autre chemin (un hook différent, un shortcode, ou un filtre).
Ce qui m’a sauté aux yeux à l’audit, ce n’était pas une fonction explicitement “exécutante”. C’était un enchaînement:
1) un shortcode accepte une entrée, 2) l’entrée est enregistrée ou transformée, 3) plus tard, un hook l’utilise pour construire une commande ou un appel de fichier.
Le code semblait logique en lecture linéaire. En réalité, WordPress a plusieurs chemins d’exécution, et un handler peut être déclenché dans un ordre inattendu selon le thème, les plugins et les conditions de rendu.
Délimiter le périmètre: thème, plugins, mu-plugins, et code “custom”
Pour un audit efficace, je distingue:
- le thème, les plugins, les mu-plugins (souvent oubliés), le “custom code” dans le thème ou via un plugin d’extraits.
Les mu-plugins chargés tôt peuvent modifier des hooks avant même que vous ayez la chance d’appliquer vos garde-fous. J’ai déjà vu un mu-plugin introduire un filtre global, en apparence anodin, puis ce filtre s’applique à du contenu non prévu. Résultat: un XSS ou une altération de rendu.
À l’échelle d’un site réel, le tri est votre meilleur allié: moins vous perdez de temps à analyser du code qui n’a aucune interaction avec l’entrée utilisateur, plus vous avancez.
Méthode d’audit: du repérage au correctif
Sans méthode, l’audit devient une lecture interminable. Avec une méthode, vous arrivez à décider quoi corriger, dans quel ordre, et comment vérifier que ça ne casse pas le site.
Je recommande un flux de travail en trois temps.
1) Inventaire rapide des hooks et shortcodes
Commencez par lister ce qui est enregistré. L’idée n’est pas de tout comprendre dès le premier jour, mais de cartographier les zones.
Pour les hooks, cherchez les appels add_action, add_filter et, dans certains cas, les “calls” dynamiques. Notez le fichier, la fonction callback, et le hook ciblé.
Pour les shortcodes, repérez les add_shortcode et regardez les fonctions callback. À ce stade, je m’intéresse surtout à: quels attributs sont acceptés, où ils sont utilisés, et si le rendu est échappé.
2) Classification des données d’entrée
Ensuite, pour chaque callback potentiellement sensible, identifiez la source.
Entrées “directes”:
- paramètres de requête, corps de formulaire, attributs de shortcode, champs de meta en base, options.
Entrées “indirectes”:
- données déjà passées par des filtres, contenu éditorial (posts) pouvant contenir des shortcodes, données importées via CSV, API, ou synchronisation.
Le piège est de croire que “si c’est dans la base, c’est sûr”. Un contenu éditorial peut être manipulé par un rôle inférieur, ou par un compte compromis. Donc il faut traiter la base comme un flux potentiellement non fiable, selon votre modèle de confiance.
3) Check de contrôle d’accès et d’encodage de sortie
Pour chaque zone, je vérifie au minimum:
- le contrôle de capacité au début des handlers sensibles, l’échappement correct à la sortie (selon le contexte HTML, attribut, URL, JS), et la validation stricte des entrées avant usage.
Ce triple contrôle est souvent suffisant pour éliminer les scénarios les plus courants.
Correctifs réalistes: réduire le risque sans casser le site
Un correctif parfait existe rarement. WordPress vit dans une réalité où les plugins tiers, les auteurs de contenu et les contraintes de compatibilité rendent les changements délicats.
L’approche que je privilégie est “réduire le pouvoir de l’entrée” plutôt que “ajouter des conditions partout”.
Pour les hooks
Souvent, la bonne correction est de limiter le contexte d’exécution. Par exemple, si un filtre modifie du contenu, il n’a pas besoin de s’exécuter dans tous les cas, ni pour tous les types de contenu.
Visez des garde-fous simples:
- exécuter seulement sur des post types spécifiques, vérifier le contexte avant d’agir (front vs admin), appliquer des validations sur les paramètres reçus par le callback, et éviter les inclusions ou appels sensibles sans liste blanche.
Si un hook est réellement nécessaire mais dangereux, vous pouvez aussi le remplacer par une version plus circonscrite, ou déplacer la logique vers un endroit où la sortie est contrôlée et où vous avez plus de garanties sur les données.
Pour les shortcodes
Les shortcodes gagnent à fonctionner en “mode strict”. Concrètement, si un shortcode sert à afficher un élément parmi une liste connue, la validation doit être une liste blanche, pas un “nettoyage” vague.
Quelques règles pratiques que j’ai vues fonctionner:
- Valider les attributs avec un schéma clair, valeurs attendues, types attendus. Échapper la sortie à chaque endroit où le contexte change. Refuser les attributs inattendus. Pas “ignorer”, refuser. Si le shortcode déclenche une récupération de données (posts, fichiers, URL), limiter le scope à ce qui est légitime.
Une nuance importante: trop de refus peut casser des pages existantes. Je traite ça comme un changement progressif, en commençant par journaliser ce qui serait rejeté, puis en durcissant.

Pour les exécutions dangereuses
Quand vous trouvez un chemin qui peut conduire à une exécution, la correction doit être radicale. Les “petites améliorations” ne suffisent pas si le principe reste le même.
Selon le cas, vous aurez un de ces choix:
- supprimer la fonctionnalité dangereuse, la restreindre à un périmètre très réduit, par exemple à l’espace admin avec des capacités fortes, remplacer la logique par une alternative non exécutable ou non interprétée.
Je préfère toujours supprimer ou remplacer plutôt que “sanitizer pour que ça passe”. Dans ces zones, le coût d’une erreur est élevé.
Un exemple de trajectoire: de la découverte à la correction
Sur un site que j’ai audité récemment, le symptôme était discret: quelques pages semblaient “altérées” après publication, parfois seulement pour certains rôles. Au premier regard, tout était à jour, plugins récents.
En creusant, j’ai trouvé une combinaison:
- un hook appliquait un filtre au contenu pour traiter une syntaxe de shortcodes, le shortcode associé acceptait un paramètre “template” censé pointer vers un nom interne, la fonction transformait ce nom en chemin et incluait le fichier correspondant, le tout semblait protégé car seuls les administrateurs pouvaient écrire ce contenu.
Sauf que le compte utilisé par l’administrateur était parfois “assisté” par un auteur disposant d’une permission de publication, et le contenu pouvait contenir des shortcodes injectés. Le code ne validait pas la liste de templates autorisés.
La correction a été faite en deux temps:
- validation stricte du paramètre, liste blanche des gabarits autorisés, et suppression du mécanisme d’inclusion dynamique remplacé par une résolution contrôlée.
Le résultat a été net: même en forçant l’attribut via contenu, le système ne pouvait plus atteindre le chemin. Ce n’était pas un patch “par chance”, c’était un changement de modèle: le paramètre ne choisit plus une ressource arbitraire.
Vérifications de sécurité après modification
Une fois les corrections faites, ne vous contentez pas de “ça marche”. WordPress a des chemins d’exécution multiples. Il faut tester les contextes.
Je fais ces vérifications, sans vous noyer dans une liste exhaustive:
- Pages publiques, pas seulement l’admin. Les filtres de rendu peuvent se déclencher dans les templates. Contenu avec shortcodes et variantes d’attributs. Utilisez du contenu de test, ou une copie de page. Cas front vs admin. Un même hook peut avoir des paramètres différents selon le contexte. Rôles différents. Auteur, éditeur, administrateur. Ce qui est autorisé pour un rôle doit rester interdit pour un autre. Vérification des logs. Si vous ajoutez une journalisation légère pour détecter des entrées rejetées, elle doit être retirée ou limitée ensuite.
Voici une petite checklist de triage, utile juste après un audit:
- Repérer tous les add_action et add_filter avec des callbacks dans le code custom Repérer tous les add_shortcode et analyser les attributs acceptés Marquer les chemins où une entrée utilisateur touche inclusion, commande, URL ou requête Vérifier les contrôles de capacité au début des handlers sensibles Confirmer l’échappement de sortie selon le contexte HTML, attribut, URL
Liste de remédiations fréquentes, sans magie
Vous trouverez ci-dessous des types de correctifs qui reviennent souvent lors d’un audit hooks et shortcodes. L’idée n’est pas de tout appliquer aveuglément, mais de savoir quoi regarder et comment décider.
1) Validation stricte des attributs de shortcodes, avec liste blanche
Si un shortcode accepte un identifiant, il doit vérifier qu’il correspond à une ressource autorisée.2) Échapper systématiquement la sortie selon le contexte
Un attribut HTML et un nœud HTML ne s’échappent pas de la même façon. Pareil pour les URLs.3) Contrôle de capacité au début des callbacks sensibles
Le check doit arriver avant toute logique à risque.4) Réduction du scope d’exécution des hooks
Limiter le hook aux post types, aux conditions et aux contextes nécessaires.5) Suppression ou remplacement des mécanismes d’exécution dynamique
Si le design implique une inclusion dynamique basée sur entrée utilisateur, remplacez par une résolution contrôlée.Pièges d’audit: ce qui fait perdre du temps (et parfois de la sécurité)
Il y a des erreurs que je vois souvent, même chez des équipes sérieuses.
- Chercher uniquement “les mots interdits” Un problème peut exister sans eval, sans base64, sans exec. Le chemin peut être “propre” en termes de mots, et dangereux en termes de contrôle. Supposer que “l’entrée vient de l’admin” Un compte admin n’est pas un bouclier éternel. Un plugin, un rôle trop permissif, ou une session compromise changent la donne. Votre modèle de menace doit être assumé. Corriger la sortie, pas la source Si l’entrée non validée est utilisée pour choisir une ressource ou construire une requête, le risque persiste même si vous “nettoyez” plus tard. Ne pas tenir compte des mu-plugins et du chargement précoce Les mu-plugins sont parfois l’endroit où l’on “cadre” tout, et parfois aussi l’endroit où l’on contourne le cadre. Les oublier rend l’audit incomplet.
Comment prioriser quand il y a beaucoup de code
Un audit sérieux se heurte à une réalité: vous ne corrigerez pas tout en une semaine. Il faut prioriser, et la priorité doit refléter le risque et l’impact.
Je priorise généralement:
- les callbacks exposés à une entrée contrôlable (shortcode côté contenu, champs post, paramètres de requête), ceux qui se branchent sur des hooks de rendu ou qui transforment du HTML, ceux qui touchent inclusion, chemins, requêtes ou transformations proches d’exécution.
Ensuite, je fais la part des dépendances: un hook peut être “dangereux” mais rarement déclenché. Un autre peut être très fréquent mais moins impactant. Le bon ordre, c’est celui qui réduit vite la probabilité d’une exploitation réelle.
Ce que je recommande pour durer: règles de développement et hygiène
Une fois le site sécurisé, la question devient: “comment empêcher que le problème revienne quand quelqu’un ajoute un hook, ou un shortcode, dans trois mois”.
Sans transformer WordPress en bunker, vous pouvez instaurer des règles simples et applicables:
- définir un style de validation pour les shortcodes: liste blanche quand un paramètre choisit une ressource, exiger l’échappement de sortie dans chaque callback qui produit du HTML, exiger des contrôles de capacité en tête des handlers sensibles, et documenter les hooks custom utilisés pour éviter qu’un nouvel ajout ne réintroduise une combinaison dangereuse.
Ces règles réduisent la probabilité que les mêmes classes de bug réapparaissent.
Un mot sur l’approche “défense en profondeur” côté WordPress
WordPress ne remplacera pas un durcissement serveur solide, ni une gestion de mises à jour rigoureuse. Mais sur la partie hooks, shortcodes et exécutions dangereuses, la défense en profondeur prend une forme très concrète:
- limiter la surface des entrées, réduire le pouvoir des paramètres, borner les contextes d’exécution, et fermer les chemins qui permettent d’atteindre une ressource arbitraire.
C’est une sécurité de système, pas juste un patch ponctuel.
https://gardewp.fr/securite-wordpress/Si vous voulez, décrivez-moi votre cas (type de site, plugins principaux, et où vous avez observé le comportement suspect). Je peux vous proposer une grille d’audit adaptée, et une méthode de priorisation plus précise, sans passer par des listes trop longues ni des corrections inutiles.