Quand on parle de sécurité WordPress, on pense vite aux plugins, aux mises à jour et aux mots de passe. C’est normal. Pourtant, une grande partie des failles exploitables passe aussi par le thème. Un thème trop permissif, des fonctions de sortie trop “confiantes”, des formulaires mal encadrés, ou un child theme bricolé sans discipline peuvent transformer un site correctement maintenu en cible facile.
Durcir le thème ne veut pas dire réécrire tout le moteur de WordPress. L’objectif est plus pragmatique: réduire les surfaces d’attaque, empêcher les données non fiables de devenir du HTML ou du code, et éviter les erreurs de configuration qui ouvrent la porte à des scénarios d’injection. Dans cet article, je vais me concentrer sur les thèmes et les child themes, avec des exemples concrets et des choix qui se font au cas par cas.
Pourquoi le thème est une surface d’attaque
Le thème n’est pas un simple habillage. Il décide comment WordPress affiche le contenu, comment il construit des pages à partir d’entrées utilisateur (recherche, formulaires de contact, champs personnalisés, commentaires), et comment il expose des endpoints via des hooks.
Même si WordPress et ses plugins font leur travail, un thème peut:
- injecter du contenu non échappé dans des attributs HTML (XSS), exécuter du PHP sur des données qui ne devraient jamais être traitées comme “code”, charger des fichiers ou des scripts de manière non contrôlée, proposer une logique d’authentification ou de permissions trop permissive.
J’ai déjà vu un site, “à jour partout”, compromettre son admin non pas via un plugin populaire, mais via une portion de template d’un thème custom. Le responsable avait ajouté une personnalisation rapide, un champ “texte” provenant d’une option WordPress injecté tel quel dans la page. Résultat: une faille d’affichage qui semblait anodine, jusqu’au moment où quelqu’un a su quoi y mettre et comment déclencher l’exécution.
Les règles de base: traiter le contenu comme non fiable
La première discipline, c’est mentalement. Tout ce qui arrive au thème comme “donnée” doit être considéré comme potentiellement hostile. Cela inclut:
- le contenu des articles et pages (même si c’est “chez vous”), les métadonnées, options, champs personnalisés, les valeurs issues de requêtes GET et POST, ce qui vient de plugins qui appellent vos templates.
Concrètement, dans vos templates, vous devez systématiquement échapper avant d’afficher.
- Pour du HTML “sûr” ou balisé: échapper selon la politique voulue (souvent wp_kses). Pour du texte simple: esc_html, esc_attr selon le contexte. Pour des URL: esc_url. Pour des noms de classes et attributs: sanitize_html_class ou esc_attr selon le flux.
Le point clé est le contexte. Échapper “à peu près” ne suffit pas. Une URL dans href ne se traite pas comme du texte dans un paragraphe. La même donnée, injectée différemment, peut basculer d’un affichage inoffensif à une exécution de script.
Durcir les sorties dans les templates
Le plus fréquent en durcissement de thème, c’est l’audit des sorties. Cherchez les endroits où du contenu dynamique est imprimé sans contrôle.
Dans WordPress, les fonctions de sortie ont souvent leur propre sécurité, mais pas toujours. Par exemple, the_content() affiche déjà du contenu filtré par WordPress, mais si vous récupérez une option et que vous faites un echo $option;, vous sortez du cadre. Idem pour des champs custom.
Quelques zones à inspecter en priorité:
- header.php et footer.php (injections dans , scripts, balises meta), templates de recherche et pages d’archives (paramètres de query), formulaires (recherche, inscriptions, formulaires custom), pages de comptes (si le thème ajoute des éléments personnalisés), widgets et composants (souvent sources de petites régressions).
Une astuce pratique consiste à faire des recherches dans votre code pour les echo, print, ou les concatenations directes autour de variables. Ce n’est pas “sexy”, mais c’est efficace. Un thème propre a moins de points de sortie “bruts”.
Formulaires dans le thème: nonce, capacité et validation
Les formulaires sont le second grand classique. Beaucoup de sites ont des formulaires gérés par des plugins, donc vous ne touchez pas à la sécurité. Mais dès que le thème ajoute un formulaire, ou que vous modifiez un existant via un template part, vous devez intégrer le socle de sécurité.
Le socle minimum, c’est:
- un nonce (wp_nonce_field) dans le formulaire, une vérification côté traitement (check_admin_referer ou check_ajax_referer selon le contexte), une vérification de capacité si l’action touche à des ressources protégées (current_user_can), une validation et une sanitation de chaque champ avant usage.
Beaucoup de thèmes custom finissent par ajouter un bouton “mettre à jour des réglages” ou un petit formulaire “d’ajustement”. Tant que c’est un simple champ texte stocké en option, ça peut rester gérable. Mais si le thème écrit dans des options qui influencent l’affichage, ou si la donnée est réutilisée dans des attributs, vous créez une chaîne d’exploitation plausible.
Une petite approche qui évite les erreurs
Quand je dois ajouter une logique dans un thème, je m’impose une règle: le thème ne devrait pas contenir de “traitement” de requête complexe. Il peut appeler un plugin ou une classe dédiée via les hooks, mais j’évite le code qui lit $_POST dans un fichier de template. Le template doit orchestrer l’affichage, pas jouer au back-end de l’application.
Les thèmes et les endpoints: prudence avec les hooks
Un thème peut aussi contribuer à la sécurité via une gestion prudente des hooks.
Le risque typique: une fonction accroche un hook trop large, ou accepte des données non contrôlées, puis les réinjecte en sortie.
Exemples de cas où je fais particulièrement attention:
- filtres qui modifient du HTML (the_content, wp_kses mal configuré, wpautop sur un contenu non filtré), action qui traite des données pour générer des composants (ex: un shortcode ou un bloc custom), chargement conditionnel de fichiers selon une variable (si elle vient de l’extérieur, c’est une mauvaise idée).
Le durcissement ici n’est pas seulement “échapper”. C’est aussi “contrôler le chemin”. Si vous devez sélectionner un template ou un fragment, utilisez un mapping strict plutôt que “nom de fichier” construit à partir d’une entrée.
Shortcodes et templates: le piège des attributs
Les shortcodes définis dans le thème sont souvent des raccourcis pour créer des blocs, mais ils deviennent vite une surface d’attaque si vous n’assurez pas une sanitation stricte de leurs attributs.
Un shortcode peut recevoir des attributs écrits dans l’éditeur. Même si WordPress filtre une partie du balisage, l’attribut id, class, style, href ou data-* peuvent déclencher des comportements inattendus si vous les utilisez sans escaping.
Règle simple: pour chaque attribut de shortcode, décidez le type attendu.

- Un identifiant de contenu, ce n’est pas “un texte libre”. Une classe doit être normalisée. Une URL doit être validée. Un style inline est presque toujours une mauvaise idée. Si vous en avez besoin, vous devez restreindre fortement, ou mieux, remplacer par des classes.
Je recommande aussi de limiter la richesse de ce que le shortcode peut faire. Plus un shortcode “sait” faire de choses, plus la validation devient un travail permanent.
Script et styles: limiter la manière dont le thème charge les assets
Le thème contrôle aussi le chargement des scripts et des styles. Une mauvaise pratique est d’ajouter des scripts directement dans le template via des balises
Le durcissement consiste à utiliser l’API standard de WordPress pour l’enqueue.
Et surtout: ne composez pas des URLs de chargement avec des fragments non validés. Pour des assets locaux, préférez des chemins construits via get_template_directory_uri() ou get_stylesheet_directory_uri(). Pour des assets externes, fixez les domaines autorisés et vérifiez l’https.
Au passage, si vous chargez des scripts qui manipulent le DOM en se basant sur des données affichées, pensez au scénario où la donnée contient des caractères inattendus. Là aussi, ce n’est pas “un problème JavaScript”, c’est une conséquence d’une sortie HTML non échappée.
Menus, search et paramètres d’URL
Les menus et la navigation semblent hors sujet, jusqu’au jour où un paramètre GET influence un affichage.
Les pages de recherche, les archives filtrées, les paramètres de tri, les tags, les pages auteur, tout cela se reflète souvent dans des éléments du thème: titres, badges, liens de pagination, chips de filtres.
Le durcissement, ici, ressemble à un audit des attributs et du texte qui reflètent les paramètres.
- Un titre qui affiche “résultats pour terme” doit échapper le terme correctement. Un lien de pagination doit construire ses query args sans laisser la possibilité d’injecter un schéma dans une URL. Une boucle qui affiche un filtre doit éviter de construire du HTML avec des fragments non nettoyés.
Le piège est sournois: même si la page ne “semble” pas exposer de contenu exécutable, un attribut mal échappé dans une balise peut suffire.
Options, settings et Meta: ce qui est stocké doit être contrôlé à la sortie
Les options WordPress et les métadonnées peuvent être modifiées via l’admin, donc on les traite parfois comme “fiables”. En réalité, un compte compromis, un plugin qui écrit mal, ou une simple erreur de configuration côté admin, peuvent transformer une option en charge utile.
Durcir signifie:
- valider à l’entrée si vous contrôlez la gestion (par exemple dans une page de settings), échapper à la sortie dans le thème, et restreindre les types permis.
Une approche que j’utilise souvent: si l’option représente un texte, je la rends toujours comme texte, pas comme HTML. Si un HTML est indispensable, alors je limite le HTML autorisé et j’applique une politique cohérente.
Child theme: la sécurité par la discipline
Le child theme est souvent présenté comme la bonne manière d’éviter d’écraser le thème parent. C’est vrai, mais ce n’est pas uniquement une question de maintenance. Un child theme peut aussi améliorer la sécurité si vous limitez les modifications au strict nécessaire et vous évitez les “copier-coller” de gros blocs de code.

Quand un child theme devient dangereux, c’est souvent pour une raison simple: quelqu’un a pris une portion du thème parent et l’a modifiée pour “ajouter un champ”, puis a laissé des pratiques fragiles identiques au parent. Ou bien, pire, le child theme surcharge des hooks globaux avec une logique improvisée.
Mon critère de confiance pour un child theme
Je préfère un child theme où:
- les ajouts sont factorisés et testables, les templates surchargés le sont uniquement là où c’est utile, les nouvelles fonctions sont courtes, avec des sorties échappées.
Je fais aussi attention aux différences entre “ajouter une fonction” et “remplacer un composant”. Si vous remplacez un fichier entier, vous héritez de tout le risque que le parent ne contrôlait pas. Si vous ajoutez une action ciblée, vous réduisez le champ.
Remplacer plutôt que patcher au hasard
Un sujet sensible: quand une partie du thème est fragile, faut-il corriger dans le parent ou uniquement dans le child theme?
En pratique, si vous modifiez le parent, vous perdez la correction au prochain update. Si vous corrigez dans le child theme, vous devez vous assurer que la surcharge est bien prioritaire et que vous ne créez pas une nouvelle incohérence.
Une règle que j’applique souvent: corriger dans le child theme uniquement quand la surcharge est sûre et spécifique. Sinon, il vaut mieux envisager un thème parent plus sain, ou contacter le fournisseur pour un patch. Le durcissement n’est pas seulement “mettre un pansement”, c’est réduire la probabilité d’une régression.
Checklist d’audit rapide (thème et child theme)
Voici la liste de contrôle courte que j’utilise pour cadrer un audit sans y passer une semaine. Elle ne remplace pas un examen détaillé, mais elle repère très vite les zones à risque.
Vérifier toutes les sorties echo, print, concatenations avec des variables depuis la base, les options, les paramètres GET/POST, ou les attributs de shortcodes. Contrôler le traitement de tout formulaire inclus ou modifié par le thème, nonce, capacités, sanitation et validation. Examiner les shortcodes définis dans le thème, en particulier les attributs utilisés dans des URL, attributs HTML, et contenus injectés. Revoir les chargements d’assets, pas de construction d’URL libre, usage d’enqueue et chemins issus des fonctions WordPress. Identifier les hooks dans functions.php qui modifient du contenu, et vérifier que l’échappement se fait selon le contexte.Si vous ne faites que ces cinq points, vous couvrez déjà une bonne part des problèmes qui finissent en incidents.
Le piège de functions.php: trop de logique dans un seul fichier
functions.php peut devenir un dépotoir. Sur un projet long, je vois souvent:
- du code de shortcode et d’ajax, des scripts de template helpers, des fonctions de traduction, des conditions de chargement, et parfois du code de traitement des requêtes.
Tout mettre dans functions.php augmente le risque d’erreur, parce que personne ne sait plus exactement quelles données entrent et sortent. Sur le plan sécurité, c’est aussi compliqué à auditer.
D’un point de vue durcissement, j’essaie de:
- isoler la logique dans des fichiers dédiés (même dans un child theme), documenter les entrées attendues, et garder les templates orientés affichage.
Ce n’est pas une question de propreté pour la propreté. C’est une barrière contre les erreurs “accidentelles” qui deviennent des vulnérabilités quand elles touchent à des points d’entrée.
Edge cases qui comptent vraiment
Certains problèmes ne se voient pas en lecture rapide. Voici quelques cas que j’ai rencontrés, et comment raisonner.
Cas 1: attributs HTML dans des balises “data-”
Vous pouvez échapper comme texte, mais si vous injectez dans un attribut data-* puis que vous relisez côté JavaScript, vous recréez parfois une surface d’injection différente. Le bon réflexe reste esc_attr, et côté JS, traiter les données comme non fiables aussi.
Cas 2: “permettre un peu de HTML” dans une option
On part d’une demande simple: “on veut coller un lien stylé dans une option”. La tentation consiste à autoriser largement le HTML avec une sanitization trop permissive. Résultat: des balises inattendues, des attributs dangereux, ou des images gérées de manière surprenante.
Si le besoin est réel, je préfère une stratégie restrictive: autoriser uniquement certaines balises, retirer les attributs non essentiels, et imposer des URL sûres. Si la page ne doit pas afficher d’HTML, gardez l’option en texte.
Cas 3: surcharge de templates pour des rôles spécifiques
Quand un thème surcharge des templates, il peut accidentellement afficher des éléments réservés à certains rôles. Ce n’est pas toujours une “faille” au sens injection, mais c’est une exposition de données.
Le durcissement côté thème, c’est de vérifier les conditions d’accès, pas uniquement de “cacher” l’interface. Par exemple, ne vous contentez pas de ne pas afficher un bouton, si la logique de rendu des données n’est pas protégée.
Un exemple réaliste de durcissement (sans magie)
Imaginons un child theme avec une option “texte d’accueil” administrable. Au départ, quelqu’un a mis:
- récupération de l’option, echo dans un .
Si l’option contient du HTML et que vous n’appliquez aucune sanitization, une personne qui modifie l’option (ou qui exploite une autre faille) peut injecter du contenu. Même si votre rôle admin est strict, un compte compromis ou une erreur de permissions sur l’API de WordPress peut suffire.
Le durcissement consiste à décider le mode d’affichage:
- Si c’est du texte, vous forcez esc_html. Si c’est du HTML très limité (par exemple, uniquement , , ), vous construisez une whitelist cohérente via wp_kses, avec une validation des attributs autorisés.
Le résultat est moins “libre”, mais plus sûr. Et souvent, les utilisateurs finissent par ne pas ressentir la perte, tant que le rendu répond à leur besoin principal.
Checklist de corrections prioritaires (quand on manque de temps)
Quand vous êtes en mode “on doit stabiliser rapidement”, cette mini liste aide à choisir les actions les plus rentables.
Remplacer chaque sortie brute par une sortie échappée adaptée au contexte (texte, attribut, URL). Ajouter nonce et vérification des capacités à chaque traitement lié à un formulaire ou à une action AJAX. Revoir les shortcodes qui injectent des attributs ou des URL, réduire la surface, valider strictement. Supprimer les constructions d’URL ou de chemins de fichiers à partir de variables non validées. Nettoyer functions.php et découper la logique, pour rendre les flux de données traçables.C’est rarement glamour, mais c’est là que la sécurité gagne du terrain.
Ce qu’il ne faut pas faire: “sécuriser” en blindant sans comprendre
Il existe aussi des anti-patterns. Par exemple:
- essayer d’échapper “en sortie” avec la mauvaise fonction parce que “ça marche chez moi”, supprimer des filtres WordPress ou désactiver des sanitizations au lieu de les ajuster, ajouter des règles “au hasard” qui cassent l’éditeur et contournent ensuite les problèmes.
Sur un thème, chaque décision peut avoir un impact sur la compatibilité, notamment pour les blocs, les shortcodes existants, ou des plugins qui s’attendent à recevoir un certain type de contenu.
La bonne posture est de corriger en gardant le comportement attendu, mais en renforçant la discipline sur les données non fiables.
Comment valider sans casser
Après des modifications de durcissement, il faut valider. Pas besoin de tout automatiser, mais il faut être méthodique.
- Vérifiez les pages publiques qui utilisent ces templates. Testez des scénarios avec des caractères spéciaux dans les champs: guillemets, chevrons, URL avec paramètres. Essayez un cas avec HTML contenu dans un champ si vous l’autorisez, et confirmez que seul le whitelisté passe. Rejouez les formulaires et les actions, et vérifiez que le nonce et la capacité ne bloquent pas des utilisateurs légitimes.
J’aime aussi faire un test “nuisance” mais réaliste: ajouter dans un champ un texte contenant
Conclusion implicite: le thème ne doit pas être la chaîne faible
Durcir un thème, c’est surtout retirer des surprises. Moins votre thème fait de promesses sur la confiance des données, plus il résiste aux erreurs et aux tentatives d’exploitation.
Si vous devez retenir une idée, c’est celle-ci: un thème sécurisé n’est pas un thème “verrouillé à l’excès”. C’est un thème qui sait traiter des entrées non fiables, échapper correctement selon le contexte, et refuser ce qui n’est pas attendu. Le reste, c’est https://gardewp.fr/securite-wordpress/ de la maintenance, et une bonne séparation entre affichage et logique.
Si vous voulez, je peux aussi vous proposer une méthode d’audit plus systématique (avec une grille de vérification par fichiers typiques: header.php, footer.php, functions.php, template parts, shortcodes), ou une checklist adaptée à votre configuration (block theme, Classic Theme, plugins de champs, présence de formulaires).