Audit de sécurité WordPress : mettre en place une Content Security Policy

Pendant longtemps, on a traité la sécurisation de WordPress comme une affaire de plugins, de mises à jour et de permissions. Puis, sur des sites exposés, j’ai vu le même scénario se répéter: un XSS oublié dans un gabarit, un script tiers chargé “parce que ça marche”, et soudain le navigateur devient le vecteur principal. À ce moment-là, la sécurité change de nature. On ne cherche plus seulement à “empêcher” l’injection, on limite ce que le navigateur a le droit d’exécuter.

C’est exactement le rôle d’une Content Security Policy, ou CSP. Elle n’élimine pas les bugs, mais elle réduit fortement l’impact. Une CSP bien pensée agit comme une barrière autour de l’exécution des scripts, des styles et de certains types de contenus. Et dans un audit et sécurisation WordPress, c’est un levier qui se combine très bien avec le durcissement applicatif, la revue des thèmes, et la réduction de la surface d’exposition.

Pourquoi la CSP change la donne sur WordPress

WordPress, ce n’est pas seulement un noyau. C’est un ensemble de couches qui interagissent: thème, modules, éditeurs, bibliothèques côté front, scripts tiers (analytics, chat, formulaires, publicités), et parfois des bouts de HTML ajoutés “manuellement” dans l’admin. La CSP devient utile quand on réalise que le risque ne vient pas uniquement de WordPress, mais de tout ce qui s’exécute dans le navigateur.

image

Prenez un exemple concret. Sur un site e-commerce, la page produit chargeait une vidéo intégrée depuis une plateforme tierce. En “ajustant” un champ de configuration, un script inline s’est retrouvé présent, sans que personne ne s’en rende compte. La chaîne d’événements a été rapide: une modification de template qui échappe mal un paramètre, puis un payload XSS. La CSP, même sans être parfaite, a empêché l’exécution de scripts inline et a stoppé la compromission avant qu’elle ne s’étende.

Le message n’est pas “mettez une CSP et tout ira bien”. Le message est plus pragmatique: une CSP transforme une compromission en incident plus local, plus détectable, et souvent beaucoup moins grave.

La première étape d’un audit: savoir ce que le site exécute réellement

Avant d’écrire des directives, il faut observer. La CSP échoue rarement “sans raison”. Elle échoue parce que le site demande des ressources depuis des endroits inattendus, ou parce que des scripts inline sont présents (ce qui est fréquent avec certains thèmes, des plugins d’optimisation, ou des tags de tracking).

Dans un audit, je commence par identifier les familles d’écarts typiques sur WordPress:

    scripts inline générés par le thème ou par un plugin (souvent via des balises script dans les templates), styles inline ou feuilles de style chargées depuis des domaines spécifiques, chargements de polices, images, iframes et connectivité fetch depuis des domaines variés, directives contradictoires causant des blocages en cascade.

L’important est aussi de comprendre les contraintes opérationnelles. Une CSP trop stricte peut casser l’interface, un formulaire, un paiement, ou une fonctionnalité “promo” qui dépend de scripts tiers.

Voici ce que je documente avant d’augmenter la rigidité de la politique:

    domaines effectifs pour les scripts, styles, images, frames et connexions présence de unsafe-inline, de scripts inline et de styles inline compatibilité requise pour les pages clés (accueil, panier, compte, pages de formulaire) dépendances à des frameworks (par exemple si un plugin injecte des bundles depuis un CDN) comportement attendu pour les pages d’erreur et les pages d’administration

Cette étape paraît longue, mais elle évite la stratégie du “je mets le maximum et on verra”. Sur WordPress, ça se paie vite.

CSP: les concepts à maîtriser, sans se perdre

Une CSP est composée de directives, chacune ciblant un type de contenu. Par exemple, on peut limiter où le navigateur peut charger des scripts via script-src, où il peut charger des styles via style-src, ou encore les connexions réseau via connect-src.

Quelques points qui reviennent dans les audits:

    script-src contrôle l’exécution et le chargement des scripts. style-src contrôle les styles, mais attention aux plugins de mise en cache ou d’optimisation qui injectent parfois du CSS inline. default-src sert de “fallback” quand les directives spécifiques ne sont pas définies. nonce-... et hash-... permettent d’autoriser des scripts inline de manière contrôlée, sans ouvrir complètement la porte à unsafe-inline. report-uri ou plutôt report-to (selon configuration et versions) servent à collecter les violations sans forcément bloquer immédiatement.

La vraie difficulté, sur WordPress, est que les scripts inline ne sont pas toujours “volontaires”. Un thème peut injecter un petit script pour un compteur, un slider ou une config de plugin, et ça suffit pour casser une politique naïve.

Démarrer en mode observation: CSP avec reporting

Le bon réflexe, quand on n’est pas certain du périmètre, est d’abord de passer en mode “report-only”. Ainsi, le navigateur signale les violations sans bloquer les ressources. Sur un site de production, c’est souvent la différence entre une amélioration contrôlée et un incident de service.

En pratique, on configure une CSP sous une forme qui n’empêche pas l’exécution, mais qui envoie des rapports. Ces rapports contiennent des informations utiles: directive concernée, origine, et parfois des éléments liés au script ou au style refusé.

Deux observations récurrentes sortent des rapports:

Des scripts inline déclenchent des violations script-src-elem ou script-src-attr, Des domaines tiers (chat, analytics, maps) ne sont pas inclus, ou sont inclus mais pas aux bons endroits (par exemple autoriser une image ne suffit pas pour autoriser un script).

Il faut aussi traiter la dimension “pages”. Une CSP peut sembler fonctionner sur la page d’accueil et casser une page spécifique, par exemple une page de paiement hébergée sur un sous-domaine différent, ou un formulaire qui charge un provider externe.

Construire une politique CSP pour WordPress: stratégie par couches

Je préfère raisonner en couches, parce que WordPress n’est pas uniforme. Une politique raisonnable commence par default-src, puis on ajoute les directives spécifiques au fur et à mesure des besoins détectés.

Dans un audit, je commence généralement avec une politique prudente qui cible d’abord les scripts, car c’est le cœur du risque XSS. Ensuite seulement je resserre styles, objets intégrés et connectivité.

Quand on doit composer avec des scripts tiers, le piège est d’ouvrir trop large. unsafe-inline ou unsafe-eval sont des raccourcis tentants. Sur un audit, on les refuse presque toujours. À la place, on cherche à autoriser au plus juste ce qui est nécessaire, via nonce, hash, et domaines précis.

image

Exemple de directives (structure conceptuelle)

Voici une série de directives souvent utiles pour cadrer la situation, à adapter selon les rapports:

    default-src 'self' script-src 'self' avec ajout de domaines tiers nécessaires, et préférence pour nonce plutôt que unsafe-inline style-src 'self' avec autorisation encadrée des styles nécessaires, parfois via nonce si l’application en produit img-src 'self' et domaines tiers d’images, typiquement CDN et plateformes connect-src 'self' pour les appels réseau des plugins (analytics, API, provider formulaires)

Ce sont des repères, pas un copier-coller. La valeur de la CSP vient de l’ajustement à votre réalité.

Gérer les scripts inline: nonce et hashes, le bon compromis

Sur WordPress, les scripts inline existent. La question devient: comment les autoriser sans rendre la politique inutile.

La méthode la plus robuste dans un contexte dynamique est l’usage de nonce. Le principe est simple: le serveur génère une valeur nonce par requête, l’insère dans les balises script que vous souhaitez autoriser, puis vous configurez script-src avec nonce-. Le navigateur n’exécute que les scripts inline qui portent ce nonce.

Le problème pratique, c’est que la politique doit être cohérente avec le rendu. Si vous n’avez aucun contrôle sur les balises script générées par des plugins, le nonce peut ne pas s’appliquer. Dans ce cas, vous avez deux options:

    attaquer le problème à la source, en modifiant le thème ou le plugin pour injecter le nonce, ou utiliser temporairement des mécanismes moins stricts, puis migrer vers une approche contrôlée.

Les hashes fonctionnent aussi, mais ils exigent que le script inline soit stable. Or, sur WordPress, beaucoup de scripts inline dépendent de données dynamiques, ce qui rend la méthode fragile.

C’est souvent là que l’audit devient intéressant. On découvre qu’un plugin ajoute un petit script inline à chaque page. Au lieu d’accepter cette injection sans contrôle, on remplace le plugin ou on patch le rendu pour externaliser le script. Une décision de design, pas seulement une décision de sécurité.

Intégrer la CSP dans l’écosystème WordPress

Une fois la politique prête, reste la question du “comment”. Sur WordPress, on ne touche pas forcément au serveur, parfois c’est l’application qui doit ajouter l’en-tête HTTP.

Selon votre configuration, vous avez plusieurs façons de déployer la CSP:

    config serveur (Apache, Nginx) via en-têtes, gestion via un module ou un plugin de sécurité, injection applicative via du code (par exemple en ajoutant l’en-tête depuis header() ou via filtres).

Mon conseil d’audit: privilégier l’en-tête au niveau serveur quand c’est possible, car c’est plus fiable et plus simple à raisonner. Mais si vous devez rester dans l’application, la discipline reste la même: une CSP cohérente, et surtout une CSP cohérente sur toutes les pages.

Un piège classique est de couvrir uniquement les pages “front office” et d’oublier:

    pages d’administration, pages de login, pages d’erreur (404, 500), contenus servis depuis des sous-domaines (par exemple assets.example.com).

Si la CSP ne s’applique pas sur l’espace d’admin, vous perdez une partie de la valeur. Si elle s’applique partout mais est mal ajustée, vous risquez de casser des fonctionnalités. L’audit doit donc inclure une cartographie de couverture.

Exemple de démarche de durcissement progressif (sans casse inutile)

Une bonne CSP se construit souvent en plusieurs passes. L’objectif est de réduire l’exécution de scripts inline et d’ouvrir uniquement les sources nécessaires, pas de faire un “big bang”.

Voici la logique que j’utilise sur des sites réels:

Activer une CSP en mode report-only, Analyser les violations sur quelques parcours utilisateur (pas seulement la home), Ajuster domaines et mécanismes (nonce, externalisation de scripts), Passer en mode bloquant sur un sous-ensemble de pages ou à des horaires où la surveillance est facile, Recommencer jusqu’à stabilisation.

Le point important est la surveillance. Sur WordPress, quand vous bloquez un script, vous bloquez aussi tout ce qui en dépend. Un rapport de violation n’est pas juste une donnée, c’est une “hypothèse” sur le contenu qui va casser. Sans suivi, vous ne savez pas si l’impact est acceptable.

Cas d’usage concrets: analytics, chat, maps, formulaires

Les scripts tiers sont rarement un problème en soi, le problème c’est leur granularité.

Un exemple fréquent: un plugin d’analytics ajoute des tags depuis plusieurs domaines, parfois en charge dynamique. Vous pensez autoriser le domaine principal, mais un script bootstrap charge ensuite des chunks depuis un CDN différent. Votre script-src doit couvrir ces origines, sinon vous perdez le tracking, mais aussi parfois des fonctionnalités UI.

Un autre exemple: un widget de chat. Selon la plateforme, il peut injecter un script inline ou charger un iframe. Vous pouvez https://gardewp.fr/securite-wordpress/ autoriser l’iframe via frame-src, mais il faudra aussi autoriser les scripts et les connexions réseau via script-src et connect-src. Si vous ne le faites pas, le widget ne se charge pas, et la régression est visible immédiatement.

Pour les maps, le motif est semblable: images et scripts depuis des domaines précis, parfois des polices, et des appels réseau depuis des endpoints spécifiques.

Dans un audit et sécurisation WordPress, j’ai appris à traiter ces cas comme des “modules”. Plutôt que d’élargir la CSP pour tout, je détermine ce qui est nécessaire pour chaque intégration tiers, puis j’associe ces domaines à des directives précises.

Les erreurs fréquentes qui rendent la CSP inefficace (ou trop risquée)

On pourrait croire qu’une CSP est simplement une liste de directives. En pratique, les erreurs rendent la politique soit trop permissive, soit inutilisable.

Les deux familles d’erreurs les plus fréquentes dans les audits:

    la politique est trop permissive, parce qu’on a conservé unsafe-inline ou des sources trop larges, la politique est trop stricte, parce qu’on a oublié un type de ressource, par exemple les polices, les frames, ou la connectivité fetch utilisée par un plugin.

Autre erreur courante: mélanger la directive “pour les éléments scripts” avec la directive “pour les attributs inline”. Aujourd’hui, selon la CSP et le navigateur, vous pouvez voir des violations différentes. Le résultat est que vous ajoutez un domaine, mais le problème reste parce que la violation concerne un autre angle.

Enfin, sur WordPress, ne pas tester les pages non standard est une erreur. La page 404 ou une page de recherche affiche parfois des scripts injectés par un thème ou un plugin de mise en page. Sans test, la CSP peut sembler bonne, puis casser sur un moment où personne ne surveille.

Deux “formats” de déploiement qui marchent bien

Selon votre environnement, vous devez choisir une approche compatible avec vos contraintes. Sur des sites gérés par équipe, j’ai souvent vu deux schémas efficaces.

1) CSP au niveau serveur, politique unique, ajustée avec précision

Cela fonctionne bien quand le rendu des scripts inline est limité ou quand vous pouvez externaliser ces scripts. Le gros avantage est la stabilité: un en-tête HTTP identique pour toutes les requêtes.

Le revers, c’est que les mécanismes comme nonce demandent souvent un passage par application, car la nonce est par requête. Si vous ne pouvez pas générer une nonce facilement au niveau serveur, vous devrez peut-être externaliser ou accepter une stratégie différente.

2) CSP côté application, politique dynamique, intégration avec nonce

Cela devient intéressant quand les pages injectent des scripts inline. Vous générez alors une nonce, vous l’injectez dans les balises script concernées, et vous envoyez l’en-tête CSP avec la même nonce.

Le revers est opérationnel: il faut garantir que toutes les balises autorisées reçoivent la nonce. Et il faut aussi vérifier l’impact sur les caches. Une page mise en cache sans varier sur la nonce est une recette pour la casse. Souvent, vous devez ajuster la stratégie de cache ou limiter la partie dynamique.

Dans un audit, je tranche selon le contrôle que j’ai sur le rendu et la capacité à gérer une nonce correctement.

Vérifier que tout tient: tests fonctionnels et validation réelle

La validation ne se limite pas à “le rapport ne montre plus d’erreur”. Sur WordPress, une CSP peut bloquer un comportement silencieux, par exemple une action UI qui dépend d’un script chargé tardivement.

Le test qui m’intéresse vraiment couvre:

    navigation typique (home, article, page produit si e-commerce) soumission de formulaires (et surtout la validation frontend si elle dépend de scripts) scripts tiers visibles (chat, analytics, popups) zone compte ou admin, au moins en mode lecture et au moins sur une session réelle

Il faut aussi regarder les performances indirectes. Une CSP peut déclencher une erreur côté navigateur qui stoppe le chargement de ressources. Le site peut sembler “lent” ou incomplet, et le diagnostic prend du temps si la CSP n’est pas la variable suspecte.

Réduire le risque au-delà de la CSP

Une CSP n’est pas un bouclier contre l’injection à elle seule. Sur WordPress, vous avez toujours besoin de fondamentaux:

    mises à jour du noyau, thème et plugins, durcissement des permissions, suppression des scripts et fragments inutiles, revue des thèmes “sur mesure” et des custom plugins, filtrage et encodage correct des sorties.

La CSP s’inscrit comme une couche de défense complémentaire. Elle devient particulièrement efficace quand vous pouvez réduire progressivement les injections inline, car vous transformez un risque XSS en blocage au niveau du navigateur.

image

Et c’est là que l’audit prend de la valeur: vous ne faites pas seulement “ajouter un en-tête”, vous améliorez la façon dont votre site délivre ses ressources.

Une trajectoire réaliste: de “bloquer” à “durcir encore”

Au fil des cycles, on peut aller plus loin dans la précision. Les étapes varient selon votre architecture, mais il y a une tendance claire: augmenter la rigueur uniquement quand vous maîtrisez les sources.

Par exemple, après stabilisation:

    vous pouvez remplacer des unsafe-inline par des nonce ou des externalisations, vous pouvez restreindre des domaines tiers, vous pouvez supprimer des exceptions qui ne servent plus quand certains plugins évoluent.

Sur WordPress, c’est aussi une discipline de suivi: une mise à jour de plugin peut réintroduire un script inline. Sans surveillance, la CSP redevient une contrainte sans valeur.

C’est pour cela que je recommande de conserver une logique d’observation, au moins à chaque changement majeur: mise à jour plugins, refonte thème, ajout d’un nouveau tag tiers.

Les points à ne pas oublier lors de la mise en place

Avant de passer une CSP en mode bloquant, je vérifie toujours quelques aspects, parce que l’expérience montre que les “petits” oublis deviennent des crises.

1) Politique sur toutes les pages critiques, y compris login et pages d’erreur.

2) Gestion des sous-domaines, et des domaines tiers exacts, pas “à peu près”. 3) Décision claire sur inline, préférence nonce ou externalisation. 4) Capacité à diagnostiquer via reporting, puis à itérer. 5) Tests fonctionnels, pas seulement des tests de rendu.

Si vous avez l’impression que votre politique est trop compliquée, c’est souvent un signal. Le site fait trop de choses dans le navigateur, avec trop de dépendances. Plutôt que d’ouvrir la CSP au maximum, l’audit peut aussi servir à simplifier ce qui est exécuté.

Mini check pour cadrer votre politique (avant le mode bloquant)

Je termine par une liste courte, utile pour éviter les erreurs de dernière minute. Elle n’a pas vocation à remplacer l’analyse, mais à sécuriser le passage de report-only vers bloquant.

    Avez-vous identifié toutes les origines nécessaires pour script-src, style-src, img-src et connect-src ? Les scripts inline autorisés sont-ils contrôlés (nonce ou hash), pas via unsafe-inline ? La CSP est-elle appliquée sur les pages qui comptent, y compris login et erreurs ? Disposez-vous d’un reporting actif pour continuer à apprendre après la mise en place ? Avez-vous validé les parcours utilisateur importants, formulaires inclus ?

Une Content Security Policy bien déployée ne rend pas votre site “impossible à attaquer”. Elle rend l’attaque moins rentable, plus bruyante, et souvent moins dommageable. Dans le cadre d’un audit et sécurisation WordPress, c’est une étape où la technique rejoint le pragmatisme, parce que l’objectif final n’est pas la conformité à une norme, c’est la réduction concrète du risque.