Header CSP : protéger contre les scripts malveillants sur WordPress

Sur un site WordPress, on pense souvent aux correctifs, aux mises à jour et à la dureté côté serveur. Pourtant, une partie des attaques modernes ne passe pas seulement par une faille en profondeur. Elle s’appuie sur un script qui s’exécute au mauvais moment, sur une balise insérée, ou sur un navigateur qui a le réflexe de faire confiance à des sources auxquelles il ne devrait pas accéder.

C’est là que la CSP entre en scène. La plupart des gens découvrent la Content Security Policy en lisant un ticket de sécurité qui parle de “blocked by CSP”, ou en voyant un rapport de violation. Mais pour vraiment protéger un WordPress professionnel, il faut aller au delà de la simple phrase “activez CSP”. La bonne approche, c’est une politique adaptée à votre site, déployée au bon endroit, et validée avec méthode.

Dans cet article, je vais expliquer comment utiliser le header CSP pour limiter l’exécution de scripts malveillants sur WordPress, avec des exemples concrets, les pièges fréquents, et les choix pragmatiques qui font la différence sur le terrain. Et au passage, on gardera en tête l’objectif de sécurité site WordPress professionnel, sans casser l’existant.

CSP, en clair : ce que le navigateur vous permet vraiment de contrôler

La CSP n’est pas un pare-feu au sens strict. C’est une règle que vous déclarez au navigateur. En gros, vous lui dites : “Voici les types de contenu autorisés, depuis quelles sources, et dans quelles conditions”.

Pour la question des scripts malveillants, les directives les plus importantes sont généralement :

    script-src : où le navigateur peut charger et exécuter des scripts object-src et base-uri : souvent utiles pour réduire l’angle d’attaque style-src : pour limiter les styles, utile notamment si vous autorisez ou non les inline script-src-attr et style-src-attr : contrôle des attributs inline dans certains cas nonce-... ou hash-... : mécanismes pour autoriser précisément certains scripts inline

Le point essentiel, c’est que CSP ne “détecte” pas l’attaque. Elle empêche l’exécution d’un contenu si celui ci ne respecte pas vos règles. Si un attaquant réussit à injecter une balise script depuis une source non autorisée, le navigateur refusera de l’exécuter.

Sur WordPress, cette logique est particulièrement intéressante, parce que l’écosystème de plugins, de thèmes et de CDN fait que les pages finissent vite par “toucher” à beaucoup d’origines. Une CSP trop agressive bloque alors Google Analytics, un tag marketing, un script de chat, ou même un bloc Gutenberg qui attend une ressource précise.

Le bon niveau de sécurité, c’est souvent un compromis mesuré, progressif, et contrôlé.

Pourquoi “au niveau du header” change tout

La CSP peut être posée via plusieurs mécanismes : un tag meta dans le HTML, ou un header HTTP. Le choix du header a des avantages pratiques.

D’abord, le header s’applique de manière plus fiable sur toutes les pages, surtout quand vous avez une configuration centralisée. Ensuite, certains cas d’injection peuvent tenter d’influencer le contenu du document avant que le navigateur lise le meta tag. Avec un header, la règle est reçue avec la réponse HTTP, ce qui donne un comportement plus prévisible.

Sur WordPress, vous pouvez déployer le header au niveau du serveur, ou via un plugin. Le mieux dépend de votre contexte : serveur Apache ou Nginx, présence d’un CDN, emploi de Cloudflare, etc.

Dans une installation WordPress professionnelle, le plus solide est souvent de gérer CSP côté reverse proxy ou sur votre configuration web, puis de faire des ajustements fins à mesure que vous testez. C’est moins flexible qu’un plugin, mais plus stable. Côté plugin, l’avantage est l’ajustement rapide. L’inconvénient, c’est que certains plugins ajoutent des headers sans cohérence globale, ou surchargent la configuration de manière inattendue sur certaines pages (pages admin, login, pages générées, ou endpoints AJAX).

Les directives “script” qui font gagner le plus sans tout casser

Quand on parle de “protéger contre les scripts malveillants”, la directive clé est script-src. Sur WordPress, elle doit composer avec :

    les scripts de WordPress core ceux des thèmes (et parfois des bundles, ou des dépendances jQuery si votre thème en a besoin) les scripts des plugins (captcha, formulaires, vidéo, carrousels, chat, etc.) les scripts tiers (analytics, pixel, tags publicitaires)

Le piège, c’est d’ajouter “*” ou un wildcard partout, ce qui rend la CSP moins utile contre l’injection. Exemple typique : si script-src * ou script-src 'unsafe-inline' est trop large, un attaquant peut exploiter l’injection et continuer d’exécuter.

Le bon réflexe consiste à viser un script-src aussi restrictif que possible. Ensuite, vous gérez l’inévitable “besoin de inline” avec des choix maîtrisés : nonce, hashes, ou réduction des inline.

Dans la pratique, beaucoup de sites finissent avec une politique qui, au départ, est en mode rapport uniquement (Content-Security-Policy-Report-Only), puis bascule progressivement en enforcement. C’est une approche saine : vous observez les violations dans vos logs avant de casser la prod.

Exemple de politique CSP pour WordPress (base raisonnable)

Voici un exemple de CSP “base” que j’ai déjà vu fonctionner sur des sites WordPress sans injection d’inline complexe. Ce n’est pas une recette universelle, mais une base de discussion :

Content-Security-Policy: default-src 'self'; script-src 'self' https: 'nonce-abc123'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; upgrade-insecure-requests

Quelques points à comprendre :

default-src 'self' pose une politique de base restrictive. Ensuite, script-src étend ce qui est nécessaire. script-src 'self' https: autorise les scripts depuis votre domaine et depuis HTTPS. Si vous avez des scripts depuis un domaine spécifique (par exemple un CDN de votre plugin vidéo), vous remplacerez https: par des domaines plus précis. nonce-... est une approche robuste pour les scripts inline. Mais elle demande que votre code injecte le même nonce dans les balises script autorisées. object-src 'none' bloque l’usage de plugins type Flash, embedding dangereux, etc. frame-ancestors 'none' empêche le site d’être embarqué dans une iframe, ce qui limite certaines formes de détournement (sans remplacer un anti-clickjacking plus global, mais c’est déjà utile).

Dans WordPress, le nonce est la partie la plus délicate, parce que beaucoup de thèmes et de plugins génèrent des scripts inline sans nonce. Si votre parc comporte beaucoup de scripts inline, vous pouvez démarrer autrement, puis durcir progressivement.

Il existe aussi une approche script-src 'unsafe-inline' au départ pour ne pas casser. Je l’ai utilisée en phase de stabilisation, mais je la considère comme un “pansement” temporaire. Tant que vous pouvez éliminer ou nonce les inline scripts, vous gagnez réellement en protection.

Le vrai travail : trouver vos sources légitimes sans ouvrir la porte trop grande

La CSP n’a d’intérêt que si elle reflète correctement votre paysage réel de scripts. Or, sur WordPress, ce paysage varie selon :

    le thème actif les plugins activés la présence de page builders votre configuration cache (minification, bundling) l’usage de CDN la langue et la personnalisation de certains pages les pages spécifiques (login, checkout, API, pages de recherche, pages de membres)

J’ai déjà vu une CSP bloquer la page de contact parce que le plugin récupérait un script depuis un sous domaine non listé, par exemple static.example-cdn.com. Sur une autre installation, c’était la page d’admin qui cassait, car la CSP n’était appliquée partout sans exception.

C’est pour ça que la mise en place sérieuse se fait en deux temps : observation, puis durcissement.

Une méthode pragmatique consiste à :

    activer Content-Security-Policy-Report-Only récupérer les rapports de violation dresser une liste des domaines légitimes utilisés par vos scripts passer ensuite en mode enforcement, directive par directive

Pour les rapports, vous pouvez utiliser report-uri (selon les environnements) ou préférer report-to si vous avez une infrastructure compatible. Dans la pratique, beaucoup de gens se limitent aux logs navigateur, ce qui fonctionne, mais demande une navigation manuelle.

Procédure réaliste de déploiement (sans casser la prod)

Voici une approche que je recommande quand vous avez un site WordPress https://gardewp.fr/securite-wordpress/ vivant, avec des plugins variés, et des utilisateurs qui ne doivent pas perdre des fonctionnalités.

Démarrez en Report-Only pendant quelques jours, avec une politique resserrée mais pas extrême. Activez un reporting vers un endpoint ou exploitez des journaux côté navigateur/serveur selon votre infrastructure. Traitez les violations en priorisant les scripts (car c’est votre objectif) et en ajoutant des origines précises au lieu de wildcard. Introduisez un nonce si vous contrôlez le HTML de vos scripts inline, ou si votre plugin permet un support nonce. Basculez progressivement en enforcement une fois que les pages critiques (accueil, formulaires, parcours de conversion, pages membres) ne cassent plus.

Pour rendre ça plus concret, imaginons que votre CSP actuelle empêche https://www.googletagmanager.com d’être chargé. Les rapports vous le signalent. Vous ajoutez alors seulement https://www.googletagmanager.com et https://www.google-analytics.com (ou les domaines exacts que vous observez) plutôt que https: générique, si vous pouvez. Ensuite, vous testez.

Le but n’est pas la perfection du premier coup. Le but, c’est une trajectoire.

Les deux pièges les plus fréquents sur WordPress

1) La CSP appliquée à tout, y compris l’admin et les endpoints AJAX

Sur WordPress, l’admin et certaines pages AJAX ont des patterns de chargement différents. Un plugin peut charger des ressources spécifiques, ou appeler des endpoints qui ajoutent des scripts via réponses.

J’ai déjà vu des sites avec une CSP qui n’autorisait pas connect-src pour certains appels fetch. Résultat : l’éditeur Gutenberg charge mais certaines fonctionnalités échouent en silence. Ou alors la console JavaScript crie, mais l’utilisateur ne comprend pas.

Même si votre CSP cible “scripts”, les directives connexes comptent beaucoup. connect-src doit souvent inclure les domaines vers lesquels vous faites des requêtes XHR et fetch. Sinon, même un script autorisé ne pourra pas communiquer.

2) Les scripts inline de plugins et de thèmes

La CSP devient frustrante quand elle rencontre le “tout inline”. Beaucoup de plugins injectent des scripts dans le head sans nonce, ou des inline handlers.

Si vous passez trop vite à une politique strictement basée sur nonce, vous allez tout casser ou forcer un retour arrière. Dans ce genre de situation, je privilégie une démarche :

    d’abord, autoriser de manière temporaire l’existant pour stabiliser ensuite, réduire progressivement l’inline enfin, basculer vers nonce et suppressions

Ce n’est pas “joli”. C’est efficace.

Gérer les scripts inline : nonce plutôt que “unsafe-inline”

Vous entendrez souvent la solution “ajoutez 'unsafe-inline'”. C’est vrai que ça évite des erreurs immédiatement. Mais c’est aussi la porte ouverte à une bonne partie des scénarios d’injection, parce que l’inline n’est plus contrôlé.

Sur un site WordPress professionnel, la valeur de la CSP vient justement de cette capacité à limiter l’exécution à des scripts autorisés explicitement.

Le nonce est donc en général plus robuste. Mais il faut le faire correctement :

    le serveur doit générer un nonce unique par requête (ou par session, selon votre modèle, mais le plus souvent par requête) la valeur du nonce doit être injectée à la fois dans l’entête CSP et dans chaque balise script inline autorisée les templates doivent coopérer pour que les scripts inline reçoivent le nonce

Avec WordPress, selon votre stack, un plugin peut parfois vous aider à mettre le nonce. Sinon, il faut travailler côté thème et plugin. Si vous n’avez pas la main sur tous les scripts inline de plugins tiers, vous pouvez être coincé sur une CSP plus permissive.

C’est là que le compromis devient nécessaire : mieux vaut une CSP “moins stricte” mais correctement configurée et progressivement améliorée, plutôt qu’une CSP extrême qui devient inutilisable.

Exemple de stratégie progressive sur un site réel

Sur un site e commerce sous WordPress, j’ai mis en place une CSP en deux vagues. La première semaine, j’ai utilisé Report-Only avec une politique qui bloquait les objets et limitait frame-ancestors. Ensuite, j’ai observé environ une dizaine de domaines qui déclenchaient des violations sur script-src et autant sur connect-src.

Les plus problématiques étaient liés à un ensemble de tags marketing et à un plugin de formulaires qui chargeait des endpoints externes.

J’ai fait deux choix qui m’ont permis d’avancer sans tout ouvrir :

image

    j’ai remplacé des wildcards par des domaines exacts observés j’ai gardé script-src strict, et j’ai assumé que certains scripts inline seraient traités plus tard

Résultat après durcissement : les attaques d’injection via une source non autorisée ont été bloquées côté navigateur. Ce que j’ai apprécié, c’est qu’on a pu garder les fonctionnalités marketing, tout en fermant la porte aux scripts inconnus.

L’autre point positif : l’équipe a arrêté de traiter la CSP comme un gadget. Une fois que vous voyez des violations concrètes et une réduction du bruit, la politique devient un outil de pilotage.

Les directives “bonus” qui renforcent la défense au quotidien

Même si votre objectif est “scripts malveillants”, il y a des directives qui complètent sans exiger une refonte :

    object-src 'none' : utile et généralement compatible frame-ancestors : protège contre le framing, bon complément à d’autres protections base-uri 'self' : limite certains détournements via upgrade-insecure-requests : évite des mix contenus, et peut réduire des scénarios de chargement involontaire en HTTP

Pour style-src, attention aux styles inline. Certains thèmes et plugins injectent des styles dans le HTML. Si vous cassez le style, l’interface devient illisible, et les utilisateurs pensent que le site “bug”.

Sur mes déploiements, je privilégie la compatibilité visuelle et je durcis d’abord la partie script. Une fois que tout fonctionne, je reviens sur style-src si nécessaire.

Un mini check avant de durcir (sinon vous perdez du temps)

Avant de passer en enforcement, il faut valider que les pages sensibles fonctionnent vraiment. Pour éviter les allers retours en soirée, je garde toujours ce mini contrôle en tête :

    vérifier la home, une page produit ou une page clé, et une page formulaire tester la connexion, la création de compte, et les pages où les formulaires se soumettent ouvrir la console et vérifier qu’il n’y a pas de violations CSP critiques contrôler les fonctionnalités qui chargent des scripts tardivement (chat, tags, conversion tracking) vérifier la version admin, parfois oubliée car “moins visible”

Cette liste ne remplace pas un test complet, mais elle évite les incidents les plus fréquents.

Comment intégrer CSP à WordPress : plugin ou configuration serveur

Vous avez deux grandes voies.

Côté plugin, vous gagnez en vitesse. Certains plugins ajoutent CSP via hooks WordPress et permettent d’ajuster les directives sans toucher au serveur. Le risque, c’est la fragmentation : vous vous retrouvez avec plusieurs plugins qui ajoutent chacun une directive ou un header, et le navigateur peut appliquer la mauvaise valeur ou ne pas appliquer du tout. Dans ce cas, c’est la confusion.

Côté serveur, vous contrôlez tout : une seule source de vérité pour les headers. Vous pouvez aussi segmenter selon les chemins, en appliquant CSP strict seulement sur les pages publiques, et une CSP différente sur l’admin. Sur un site WordPress professionnel, c’est souvent plus propre, surtout si vous avez déjà une couche Nginx ou un proxy.

Si vous utilisez un CDN ou une solution edge, gardez en tête que la CSP peut être modifiée par le proxy. J’ai déjà vu des setups où le header était injecté, puis remplacé au niveau du CDN, ou bien dupliqué. La première étape avant toute optimisation est donc de vérifier la valeur effective du header dans le navigateur, pas seulement sa valeur dans WordPress.

Ajuster la CSP sans tomber dans le wildcard éternel

Une fois que vous avez des rapports, vous risquez une tentation : ajouter “https:” ou “*” pour faire taire les violations. Je l’ai fait par précaution sur un site lors d’un incident urgent, puis j’ai corrigé plus tard. Mais l’objectif doit rester de revenir à des domaines explicites.

Si vous listez trop peu, vous bloquez du fonctionnement légitime. Si vous listez trop large, vous réduisez la protection contre l’injection. Le bon équilibre se trouve en durcissant ce qui compte, d’abord script-src, puis connect-src, et en réduisant progressivement l’ampleur des sources autorisées.

Un indice utile : si vos scripts légitimes viennent d’un petit nombre de domaines, alors une CSP stricte est réaliste. Si au contraire votre site dépend d’un grand nombre de scripts tiers et d’origines non contrôlées, il faudra prioriser et accepter une politique plus nuancée.

Cas particulier : “unsafe-eval”, et pourquoi je le limite fortement

Les directives liées à l’évaluation de code (comme unsafe-eval) sont souvent une source d’optimisation rapide mais risquée. Certains scripts utilisent eval ou des techniques proches. Autoriser unsafe-eval peut aider à faire fonctionner certaines bibliothèques, mais ça réduit la valeur de votre CSP contre l’exécution détournée.

Sur WordPress, je traite unsafe-eval comme une exception à justifier. Quand un plugin exige cette directive, je regarde d’abord si une version plus récente de plugin ou un changement de configuration résout le besoin. Sinon, je le limite à l’environnement exact où c’est indispensable, et je garde une CSP stricte ailleurs.

Mesurer l’impact : ce que vous devez observer après déploiement

Une CSP bien pensée laisse généralement un signal clair :

    moins de scripts chargés depuis des domaines non attendus des alertes CSP qui se stabilisent, donc moins de “bruit” une réduction des scénarios où un contenu injecté peut s’exécuter

Mais attention : une CSP n’empêche pas un attaquant de réussir une intrusion. Elle empêche surtout un navigateur d’exécuter ce qui ne respecte pas vos règles. Si l’attaquant arrive à modifier les pages via un plugin mal configuré ou un thème compromis, il peut parfois produire du HTML et du script qui respecte vos directives s’il contrôle aussi vos sources autorisées. C’est une raison supplémentaire de combiner CSP avec une hygiène sécurité complète : mises à jour, limitation des droits, durcissement WordPress, supervision des fichiers, et surveillance des plugins.

Les détails qui sauvent des heures : vérifier le header final et sa portée

Quand vous testez CSP, ne vous contentez pas de “ça bloque ou ça ne bloque pas”. Vérifiez aussi :

    le header effectif dans l’onglet Network (valeur réelle) les règles appliquées sur les pages ciblées la présence éventuelle de plusieurs headers CSP (parfois suite à plusieurs couches) l’existence de directives que vous n’avez pas prévues (certaines bibliothèques ajoutent des métas, ou des proxies modifient)

Sur des plateformes WordPress, c’est fréquent d’avoir une combinaison de WAF, de proxy, et de plugin de sécurité. Si deux modules ajoutent chacun une CSP, le comportement navigateur peut devenir difficile à interpréter.

Je recommande de commencer par une CSP “single source of truth”, puis d’étendre.

Deux objectifs de sécurité qui doivent rester séparés

Il est tentant de mélanger tout dans la CSP, mais j’ai appris à distinguer deux axes :

1) Limiter l’exécution de scripts venant de sources non autorisées

2) Réduire l’espace d’attaque autour du framing, des objets, et des bases URI

image

En pratique, ça aide à prioriser. Si un site a besoin d’abord de préserver le tracking et les scripts tiers, vous pouvez démarrer par des directives “faciles” et une politique centrée script-src. Ensuite, vous élargissez.

Cette approche évite le syndrome du “tout ou rien”, qui finit souvent par faire abandonner la CSP au bout de quelques jours, parce que ça casse trop de choses.

Trajectoire de durcissement : rendre la CSP utile, puis la rendre solide

La meilleure CSP n’est pas celle qui impressionne dans un exemple. C’est celle qui reste opérationnelle sur votre WordPress, sur vos pages réelles, avec vos plugins réels.

Une bonne trajectoire, c’est :

    d’abord rendre la CSP visible via les rapports ensuite ajuster précisément les origines nécessaires puis réduire l’inline en privilégiant nonce si possible enfin, renforcer les directives connexes une fois que l’écosystème ne casse plus

C’est plus lent qu’une recette, mais c’est ce qui marche sans drama.

Dernier mot sur la protection contre les scripts malveillants

La CSP est un rempart côté navigateur, puissant parce qu’il s’oppose à l’exécution de scripts non autorisés. Sur WordPress, elle devient particulièrement utile quand vous la traitez comme un produit vivant, pas comme une option qu’on active une fois pour toutes.

Si vous devez choisir une priorité, choisissez script-src. Puis travaillez connect-src pour éviter les cassures fonctionnelles. Ajoutez des directives “gagnantes” comme object-src 'none' et frame-ancestors 'none'. Et surtout, avancez par paliers avec un mode rapport, sinon vous finirez par autoriser trop large et vous perdrez la valeur protectrice.

image

Une sécurité site WordPress professionnel, ce n’est pas seulement l’empilement d’outils. C’est la cohérence entre votre configuration, vos plugins, votre déploiement, et la façon dont le navigateur interprète vos règles. La CSP est un bon levier, à condition d’être pilotée avec rigueur.