Durcissement WordPress : configurer Content-Security-Policy (CSP)

Sur un site WordPress, la plupart des incidents que je rencontre ne viennent pas d’un “hack” spectaculaire, mais d’une accumulation de détails: scripts chargés depuis ailleurs, plugins qui injectent du JavaScript en mode inline, modules publicitaires qui ajoutent des balises, et parfois même des erreurs de configuration côté serveur ou CDN. La Content-Security-Policy, CSP, sert à reprendre la main de façon pragmatique: vous dites au navigateur ce qu’il a le droit d’exécuter, où il peut récupérer les ressources, et ce que vous souhaitez bloquer.

Dans le cadre d’un durcissement WordPress, CSP est souvent l’une des protections les plus utiles… mais aussi l’une des plus délicates à “verrouiller” sans casser des fonctionnalités. La clé, c’est de procéder par étapes, d’anticiper les cas réels (et pas seulement les cas théoriques), et d’accepter que la politique parfaite n’existe pas dès le premier essai.

Comprendre ce que CSP fait, sans jargon inutile

La CSP est une règle envoyée par le serveur via un en-tête HTTP. Le navigateur l’applique au moment de charger une page. Concrètement, elle agit comme une liste de permissions. Par exemple:

    script-src contrôle les sources de scripts autorisées. style-src contrôle les feuilles de style. img-src limite d’où viennent les images. connect-src encadre fetch, XMLHttpRequest, WebSocket, et autres connexions réseau depuis le navigateur. frame-src limite le chargement dans des iframes.

Quand une ressource ne correspond pas, le navigateur refuse. Selon la directive, cela peut aussi produire un rapport (via report-uri, report-to) ou simplement bloquer silencieusement.

Le bénéfice sécurité est clair: réduire fortement la surface exploitable par du cross-site scripting (XSS) et certaines injections. Si un attaquant parvient à injecter un

Mais la réalité de WordPress, c’est que beaucoup de sites s’appuient sur des scripts inline, des balises injectées par plugins, des widgets tiers, et des styles chargés dynamiquement. Votre CSP doit donc refléter votre site, pas une idée abstraite.

Pourquoi CSP casse souvent au début sur WordPress

Sur un WordPress “standard”, il y a trois causes fréquentes de casse lors de l’activation d’une CSP trop stricte.

Première cause: les scripts inline. Beaucoup de thèmes et plugins insèrent du JavaScript inline. Même si cela semble “pratique”, CSP est très restrictive par nature. Si vous passez en mode script-src 'self' seulement, le navigateur bloquera ces inline scripts.

Deuxième cause: les ressources tierces. Analytics, tags marketing, reCAPTCHA, chat, boutons de réseaux sociaux, fonts externes, flux vidéo. Chaque composant apporte ses domaines. Oublier un domaine suffit à casser un module, parfois sans message clair dans l’interface utilisateur.

Troisième cause: les data: et blob:. Les URI de type data: peuvent être utilisées pour des images, des polices ou des éléments spécifiques. blob: intervient aussi parfois pour des médias. Autoriser “trop large” peut réduire l’intérêt sécurité, mais ne pas l’autoriser peut casser des fonctionnalités.

Le point important, c’est que CSP n’est pas un interrupteur marche/arrêt. C’est un verrou progressif. Le meilleur schéma que j’ai vu sur des sites de taille moyenne, c’est de commencer en “report-only”, d’observer, puis de durcir avec des exceptions ciblées.

Stratégie de durcissement: partir de l’observable, pas du supposé

Avant même d’écrire une seule directive, je commence par deux vérifications simples:

1) Qu’est-ce qui charge réellement sur vos pages sensibles, notamment la page d’accueil, une page typique de contenu, et le back-office (/wp-admin).

2) Quelles sources tierces sont déjà en place, souvent via des scripts de tag manager ou des plugins.

Vous pouvez faire ça via les outils de développement du navigateur, en regardant le détail des requêtes réseau. Sur Firefox et Chrome, la console et l’onglet réseau montrent assez bien les domaines appelés.

Ensuite, vous construisez une première politique en mode observation, puis vous affinez.

Une question de périmètre: tout le site ou seulement une partie

Sur WordPress, il est tentant d’appliquer CSP à l’ensemble. Sur certains sites, c’est faisable, mais sur d’autres, le back-office a des besoins spécifiques (plugins d’administration, scripts injectés différemment).

Une approche pragmatique consiste à couvrir d’abord les pages publiques, puis à étendre à wp-admin après validation. En durcissement WordPress, c’est souvent le meilleur compromis entre sécurité et stabilité, surtout si vous avez des plugins “historiques”.

Préparer une politique CSP “propre” pour WordPress

Il faut aussi comprendre les deux contraintes pratiques de CSP.

D’abord, CSP se déploie en en-tête, donc vous devez choisir où le configurer: au niveau serveur (Nginx/Apache), via un CDN/WAF, ou via un plugin WordPress. Chaque option a ses avantages, mais aussi ses limites.

Ensuite, CSP peut nécessiter une approche à base de nonces ('nonce-...') ou de hash ('sha256-...') pour autoriser du inline de façon contrôlée. Sur WordPress, les nonces sont souvent plus cohérentes, mais elles demandent que le serveur injecte un nonce dans les balises script correspondantes. Selon vos plugins et votre thème, ce niveau d’intégration peut être simple… ou délicat.

Dans la pratique, sur beaucoup de sites WordPress, je recommande de commencer en évitant le blocage immédiat du inline, puis de réduire progressivement avec strict-dynamic, nonce-*, ou des mécanismes adaptés au stack.

Les directives minimales utiles

Une politique utile commence rarement vide. Pour obtenir une base solide, vous pouvez définir au minimum:

    default-src comme valeur de repli. script-src et style-src pour encadrer l’essentiel. img-src pour éviter les chargements arbitraires. connect-src si vous avez du fetch, analytics, ou API. frame-src si vous utilisez des iframes (médias ou widgets). éventuellement font-src selon vos polices.

L’idée est de limiter ce qui peut se charger. Mais le “comment exactement” dépend de votre site.

Où configurer CSP sur WordPress

Il n’y a pas une réponse unique, mais j’ai des préférences selon les contextes.

Sur un site avec accès serveur, configurer CSP dans Nginx ou Apache est généralement le plus robuste. Les en-têtes sont stables, pas dépendants du plugin, et pas modifiés par un événement WordPress. C’est aussi plus simple à auditer sur le temps long.

image

Si vous ne touchez pas au serveur, un plugin CSP peut suffire pour démarrer en mode report. Dans les faits, c’est parfois le meilleur chemin pour un premier test. En revanche, certains plugins ajoutent eux-mêmes des en-têtes ou ajustent la politique en fonction de pages. Il faut alors vérifier que la configuration produit bien l’en-tête attendu côté navigateur.

Si vous passez par un CDN ou un WAF, vous pouvez aussi poser la CSP en edge. C’est pratique, et souvent plus fiable sur la continuité. Mais vous devez gérer la cohérence des règles avec WordPress, et attention aux environnements (staging versus production).

Quelle que soit votre option, gardez une règle d’or: valider l’en-tête effectivement reçu par le navigateur. Une politique CSP qui “semble” configurée ne sert à rien si elle n’est pas appliquée.

Démarrer en report-only: méthode qui évite les mauvaises surprises

La meilleure manière de ne pas casser un site est d’activer CSP en mode “report-only”, ce qui signifie que le navigateur n’applique pas le blocage, mais envoie des informations sur les violations. Vous voyez ce que CSP aurait bloqué.

Sur le plan pratique, cela demande un mécanisme de réception des rapports. Selon les versions et l’implémentation, vous rencontrerez différents formats. Beaucoup de gens finissent par utiliser un endpoint de logging (sur votre serveur ou via un service) ou simplement des fonctionnalités de devtools.

Le point important est moins le format exact que la boucle d’amélioration: vous observez, vous ajustez, vous réessayez.

Un mini guide de contrôle avant de durcir

Voici ce que je vérifie systématiquement avant de passer de report-only à enforcement sur un site WordPress:

    Confirmer que l’en-tête CSP est bien présent sur les pages ciblées (et pas seulement sur le HTML racine). Lister les domaines des scripts et styles chargés en production, pas en local. Tester wp-admin et au moins une page publique avec un compte utilisateur réel (pas juste un test “admin vide”). Repérer les dépendances tierces critiques (reCAPTCHA, pixels publicitaires, chat). Garder une option de rollback rapide, au moins au niveau serveur ou configuration plugin.

Cette discipline évite 80% des incidents “ça marche chez moi” après la mise en production.

Exemple de construction progressive de CSP pour WordPress

Je vais éviter de vous donner une politique “universelle”, car elle serait presque forcément fausse sur un site réel. En revanche, je peux décrire une approche concrète de construction, avec des choix typiques.

Sur un site WordPress classique, la base default-src 'self' est souvent un bon point de départ. Ensuite, vous élargissez ce qui est nécessaire. Par exemple, les scripts de plugins et de WordPress sont généralement sur 'self', mais les tiers vont nécessiter des domaines spécifiques.

Pour les images, 'self' plus certains domaines CDN peut suffire. Certains sites ont des images hébergées sur un service tiers ou utilisent des avatars externes. Dans ces cas, on ajoute des domaines précis, plutôt que d’autoriser * ou des schémas trop larges.

Pour connect-src, si vous avez un tag manager et des requêtes vers des endpoints de mesure, vous devrez déclarer ces origines. Si vous utilisez un service d’API (même pour une simple vérification), cette directive devient rapidement le point de blocage numéro un.

Enfin, pour frame-src, seulement les sources réellement utilisées doivent apparaître. Beaucoup de problèmes viennent d’un iframe oublié, souvent utilisé par un module de médias ou un widget.

Le durcissement vient ensuite: réduire les autorisations “génériques” et introduire des règles plus strictes.

Le piège des scripts inline et les options réalistes

Sur WordPress, vous verrez deux types de scripts inline.

Le premier est “navigateur-friendly”: des petits scripts de configuration, des initialisations, ou des bundles générés par un thème. Le second est plus problématique: des injections inline provenant de plugins qui n’ont pas été modernisés.

CSP vous donne plusieurs stratégies, selon votre capacité à contrôler le HTML généré.

1) Autoriser temporairement unsafe-inline (ou plutôt unsafe-inline n’existe pas tel quel en CSP moderne, mais on parle souvent de l’autorisation qui revient à accepter l’inline) pour éviter de casser pendant que vous identifiez les responsables.

2) Utiliser des nonces si la génération peut être cohérente. 3) Utiliser des hashes pour les scripts inline fixes. C’est efficace si le script ne change pas à chaque rendu, mais WordPress et les plugins génèrent parfois des variations.

Dans la vraie vie, je privilégie une stratégie par campagne: bloquer à fond sur les pages où le contenu est stable, et traiter les exceptions avec parcimonie. Si vous avez plusieurs plugins qui injectent du inline, vous pouvez finir par concentrer l’autorisation sur ceux qui “doivent” rester inline. Ce n’est pas la perfection théorique, mais c’est une amélioration réelle par rapport à “aucune contrainte”.

Politique plus stricte: intégrer les nonces (quand c’est possible)

Les nonces, c’est l’idée suivante: vous autorisez l’exécution des scripts inline uniquement si le navigateur reçoit le bon nonce dans le tag CSP. Techniquement, cela réduit beaucoup le risque, car un attaquant qui injecte du inline ne connaît pas le nonce valide.

Sur WordPress, la difficulté est d’assurer que le nonce généré est bien injecté dans toutes les balises script concernées, sans casser le rendu des thèmes et plugins. Certains plugins peuvent déjà supporter des nonces. D’autres non.

image

Le trade-off est simple: plus vous vous rapprochez d’une politique “nonce-only”, plus c’est sécurisé, mais plus c’est exigeant. Sur un site riche en plugins, le coût de mise en œuvre peut être élevé.

Mon conseil: testez avec un sous-ensemble de pages ou avec une politique report-only, puis élargissez seulement quand vous voyez que l’impact fonctionnel est maîtrisé.

Une configuration CSP robuste, mais sans tomber dans le “tout est interdit”

Il existe une tentation fréquente: “je mets tout en self et j’ajoute aucune exception”. Sur un WordPress réel, c’est rarement viable. Vous risquez de casser le login, certaines pages de checkout, les formulaires enrichis, ou des éléments marketing.

La bonne approche consiste à viser une politique “restrictive mais alignée”. Par exemple, plutôt que d’ajouter https: à la place de tout, ajoutez des domaines précis. Plutôt que data: global, autorisez-le uniquement si vous identifiez un besoin réel (images encodées, polices, etc.). Et plutôt que d’exécuter tout inline, utilisez une méthode contrôlée (nonce ou hash) si le site est adapté.

Le durcissement WordPress est rarement un one-shot. C’est un cycle.

Étapes pratiques pour passer à enforcement sans se tirer une balle dans le pied

Voici une séquence que j’utilise souvent, adaptée aux sites WordPress avec plugins:

Déployer CSP en report-only sur la page la plus critique, souvent la page publique d’entrée, puis surveiller les violations. Ajouter progressivement les origines nécessaires, en privilégiant des domaines précis plutôt que des autorisations larges. Tester en navigation réelle avec un navigateur “propre” (vider le cache, tester en navigation privée si possible). Passer à enforcement sur une portion du site, puis étendre après stabilisation.

Cette méthode limite les régressions, et surtout elle vous donne un rationnel basé sur des observations.

Particularités qui reviennent sur la majorité des WordPress

Compatibilité avec les plugins de formulaires et d’automatisation

Les formulaires utilisent souvent connect-src pour envoyer des données, parfois via des endpoints internes, parfois via des services. Si vous bloquez sans autoriser les domaines utilisés par le plugin, vous verrez des formulaires qui “soumettent” mais ne déclenchent pas les suites attendues, ou qui échouent silencieusement.

Le diagnostic se fait dans la console navigateur: erreurs liées au chargement ou au blocage CSP, et parfois des requêtes réseau annulées.

Analytics et pixels

Les outils d’analytics et de marketing chargent souvent des scripts depuis leurs domaines, et envoient des événements vers d’autres origines. Les deux aspects sont distincts. script-src contrôle où vient https://gardewp.fr/securite-wordpress/ le script, connect-src contrôle où vont les requêtes. Une politique “script ok mais connect bloqué” est fréquente.

Médias externes et iframes

YouTube, Vimeo, certains lecteurs de contenu, ou des widgets intégrés utilisent des iframes. Si frame-src n’autorise pas la bonne origine, la vidéo ou le widget ne s’affiche pas. Parfois, une partie s’affiche, mais l’interaction échoue.

Fonts et thèmes

Les polices de caractères, notamment via Google Fonts ou des CDNs de polices, nécessitent font-src. Si vous laissez la directive trop stricte, le texte peut s’afficher avec une police de secours. Ce n’est pas toujours bloquant, mais cela dégrade l’expérience, et sur certains navigateurs cela s’observe vite.

Déboguer une CSP quand “ça ne marche plus”

Quand CSP bloque une ressource, le navigateur trace souvent des messages d’erreur. Mais ces messages ne sont pas toujours explicites pour un non-spécialiste, surtout quand plusieurs directives s’appliquent.

Mon conseil est de travailler en triage:

    Commencez par identifier la ressource bloquée: script, style, image, connexion, iframe. Repérez la directive qui a refusé, et l’origine impliquée. Ajustez la politique en ciblant l’origine, pas en ouvrant toute la planète.

Si vous êtes en report-only, vous obtenez déjà une liste de violations. En enforcement, vous devez parfois reproduire avec un cache neutralisé pour être certain de voir la cause réelle.

image

Il y a aussi un piège: certains plugins modifient le DOM après chargement. CSP peut alors bloquer des scripts ou styles injectés au runtime. Dans ce cas, la correction ne vient pas seulement de la politique, mais aussi du comportement du plugin.

Choisir une politique “niveau sécurité” adaptée à vos contraintes

Selon votre environnement, vous pouvez aller de “prudent” à “très strict”.

Un niveau prudent vise à bloquer l’évidence, sans casser l’écosystème. Un niveau strict vise à ne plus autoriser d’exécution inline non contrôlée et à réduire à la portion congrue les schémas et directives permissifs.

La vraie question est: est-ce que votre WordPress supporte les nonces, les hashes, et une politique stable? Si vous avez beaucoup de plugins “anciens”, la route nonce-only peut devenir un projet en soi. À l’inverse, sur un site plus maîtrisé, avec thème et plugins suivis, vous pouvez durcir davantage.

Mon approche: viser une réduction nette du risque en premier, puis planifier le durcissement avancé sur la durée, plugin par plugin. C’est plus long, mais c’est réaliste.

Compatibilité serveur, reverse proxy et CDN

Un détail qui surprend: certains environnements doublent ou remplacent des en-têtes. Si vous configurez CSP au niveau serveur et via un plugin, vous pouvez finir avec deux en-têtes, ou avec un en-tête non celui attendu.

De plus, un reverse proxy (reverse-proxy, load balancer) peut modifier la politique, ou supprimer des en-têtes si la config n’est pas alignée.

Le bon réflexe est de vérifier le “view source” des headers côté navigateur pour chaque route concernée. WordPress peut servir des pages avec des caches différents, et donc une configuration CSP peut s’appliquer différemment selon le type de requête.

Points d’attention avant de “verrouiller”

Voici les erreurs classiques que je vois revenir quand on fait du CSP dans un cadre de durcissement WordPress:

    Oublier d’inclure les domaines utilisés en back-office. Négliger connect-src et se retrouver avec des fonctionnalités d’API cassées. Ouvrir trop large, par exemple * sur des directives sensibles, ce qui réduit l’intérêt sécurité. Activer enforcement trop tôt, sans passage report-only ou sans tests sur navigation réelle. Ne pas prévoir un rollback rapide.

CSP est une excellente barrière, mais comme toute barrière, elle doit être testée. Le coût d’un oubli, c’est parfois un site partiellement inutilisable, au moins pour certains utilisateurs.

Et si vous devez prioriser: où commencer sur WordPress

Quand on n’a pas le luxe de tout tester partout, priorisez les routes à fort enjeu. Typiquement:

    pages qui reçoivent du contenu dynamique et des formulaires, pages publiques où l’exposition est la plus large, pages où des scripts inline sont nombreux.

Le back-office est aussi important, mais il dépend beaucoup des plugins que vous utilisez. Si votre objectif est de réduire rapidement le risque, commencez par les pages publiques, observez, puis étendez.

C’est exactement la logique d’un durcissement WordPress progressif. Vous réduisez le risque d’abord là où il est le plus concret, et vous construisez une politique robuste plutôt que parfaite.

CSP n’est pas une fin en soi. C’est une brique qui s’ajoute à la mise à jour régulière de WordPress, au contrôle des plugins, à une politique de mots de passe solide et à une hygiène de publication stricte. En contrepartie, une CSP bien calibrée apporte une protection très tangible contre des classes d’attaques, et elle vous aide aussi à mieux comprendre votre surface d’exécution front-end.

Si vous construisez votre CSP comme un projet itératif, avec observation, ajustements ciblés et tests réalistes, vous obtenez quelque chose de durable, pas un réglage ponctuel qui “casse tout” au premier changement de plugin.