Content-Security-Policy sur WordPress : ce que le nonce change vraiment
1 min de lecture
Content-Security-Policy sur WordPress : ce que le nonce change vraiment
La Content-Security-Policy (CSP) est un en-tête HTTP. Elle dit au navigateur quels scripts, objets embarqués et URL de base sont acceptables pour une réponse donnée. Elle sert d’abord à limiter le cross-site scripting : du HTML injecté ne doit pas pouvoir exécuter du JavaScript. Elle ne couvre ni l’injection SQL, ni un cœur WordPress non mis à jour, et elle n’agit que sur les réponses qui l’envoient vraiment.
Sur WordPress, l’échec vient de scripts imprimés hors des API du CMS, d’un cache qui fige le nonce, ou d’une directive style-src copiée depuis une application JavaScript. L’extension Strict CSP 0.3.2, mise à jour le 9 septembre 2026, testée jusqu’à WordPress 7.1.3 et réservée à WordPress 6.4 ou plus, donne une politique courte et publique. Elle sert de point de comparaison, pas de modèle obligatoire.
Ce qu’une liste de domaines ne protège plus
Une CSP par liste d’hôtes autorise des origines, par exemple script-src 'self' https://cdn.exemple. La documentation MDN rappelle deux limites déjà mesurées : ces listes laissent passer un domaine dangereux, et elles gonflent. L’exemple qu’elle cite, tiré de « How I learned to stop worrying and love the Content Security Policy », montrait qu’une intégration Google Analytics pouvait exiger 187 domaines Google.
Le trou le plus courant sur WordPress est ailleurs. Beaucoup de failles XSS n’ajoutent aucun hôte : elles injectent un bloc <script>, un attribut onclick ou une URL javascript:. Une liste qui conserve 'unsafe-inline' laisse ce vecteur ouvert. L’audit affiche une politique, le script injecté s’exécute quand même.
Nonce et strict-dynamic
Un nonce CSP est une chaîne aléatoire, propre à une réponse, recopiée dans l’en-tête et sur les balises de script légitimes. Le navigateur n’exécute un script que si les deux valeurs coïncident. Ce n’est pas le jeton anti-CSRF de WordPress. wp_create_nonce() est lié à l’utilisateur, à la session et à une action, et il reste valable plusieurs heures. Le journal de Strict CSP 0.3.2 change explicitement de fonction : le nonce de politique vient de wp_generate_password( 22, false, false ), soit 22 caractères sans caractères spéciaux.
'strict-dynamic' appartient à la CSP de niveau 3 et ne vaut que pour les scripts. Combiné à un nonce ou à un hash, il propage la confiance du script racine aux scripts qu’il insère ensuite. Dans les navigateurs qui le comprennent, 'self', les listes d’hôtes et 'unsafe-inline' présents dans la même directive sont ignorés.
L’en-tête réellement construit par Strict CSP 0.3.2 est celui-ci.
Content-Security-Policy: object-src 'none'; script-src 'nonce-…' 'unsafe-inline' 'strict-dynamic' https: http:; base-uri 'none'
'unsafe-inline', https: et http: ne veulent pas dire que Chrome actuel autorise tous les scripts inline. MDN décrit ce schéma comme un repli : un vieux navigateur, limité à la CSP de niveau 1, retombe sur 'unsafe-inline' et les schémas ; un navigateur de niveau 3 ne retient que le nonce et 'strict-dynamic'. Supprimer ces jetons « pour faire plus propre » change le comportement des anciens clients, pas celui des navigateurs récents.
Le nonce n’est posé que si le script passe par une API du CMS : wp_enqueue_script(), wp_add_inline_script(), wp_localize_script(), wp_print_script_tag(), wp_print_inline_script_tag() ou wp_enqueue_script_module(). Ces points d’accroche viennent des tickets #39941 et #58664. Un echo de balise, encore présent dans certains thèmes par défaut (ticket #63806), est bloqué même quand le code est le vôtre.
echo '<script>document.body.classList.remove("no-js");</script>';
wp_print_inline_script_tag( 'document.body.classList.remove("no-js");' );
Les attributs onclick et les URL javascript: ne reçoivent pas de nonce. Il faut un écouteur ajouté depuis un script déjà autorisé. object-src 'none' interdit <object>, <embed> et l’ancien Flash. base-uri 'none' interdit une balise <base> injectée, qui réécrirait les URL relatives. L’auteur de l’extension note que jQuery.parseHTML() peut violer base-uri : la console signale alors une violation sans que la page perde sa mise en forme.
Le cache de page annule le nonce de deux façons
Un nonce n’est utile que s’il change à chaque réponse et reste imprévisible pour qui injecte du HTML. Un cache plein page — WP Super Cache, WP Rocket, ou le cache d’un hébergeur — stocke le HTML déjà rendu.
Première panne, visible : le HTML en cache porte nonce="aaa", mais PHP régénère l’en-tête avec nonce="bbb". Plus aucun script ne correspond. Le menu et les blocs interactifs meurent, la console répète « Refused to execute inline script ». Il faut comparer l’en-tête et les balises d’une même réponse, pas retirer la politique.
Seconde panne, silencieuse : le cache mémorise ensemble le HTML et l’en-tête. Le même nonce part chez tous les visiteurs et se lit dans le code source. Il ne prouve plus rien. Une injection qui réutilise cette valeur publique retrouve un canal d’exécution, alors qu’un audit automatique coche « CSP stricte ».
- Page dynamique : pas de cache HTML, un seul nonce pour l’en-tête et les balises de la requête.
- Page mise en cache : un jeton de substitution dans le HTML, remplacé au moment d’envoyer l’en-tête. C’est le schéma évoqué sur le ticket #39941, dans la lignée des exemples nginx de Scott Helme. La valeur finale ne doit pas être celle du fichier en cache.
- Script inline qui ne change jamais : un hash
sha256-de son contenu exact. Le hash survit au cache, et il se recalcule à chaque modification du script.
Les styles ne héritent pas de strict-dynamic
'strict-dynamic' ne s’applique pas à style-src. Aucun mot-clé ne fait confiance aux feuilles chargées par un premier fichier CSS. Une politique copiée d’un tutoriel d’application monopage ajoute souvent style-src 'self'. WordPress, Gutenberg et la plupart des thèmes injectent des balises <style> et des attributs style. Le navigateur les ignore. Le symptôme est une page sans mise en forme, pas un message d’attaque.
Strict CSP 0.3.2 ne déclare pas style-src. Les styles restent autorisés. Ce n’est pas un oubli à corriger le jour de la mise en ligne. Durcir cette directive demande un inventaire à part : hashes des blocs stables, nonce sur les balises produites par le CMS. En attendant, style-src 'self' 'unsafe-inline' est un compromis qu’il faut nommer. Il réduit peu le XSS. Il évite de confondre une régression visuelle avec une politique de script ratée.
frame-ancestors est absent de cet en-tête et n’a pas de repli sur default-src. Sans elle, une autre origine peut encore afficher la page dans une iframe. frame-ancestors 'none' ferme ce cas ; 'self' suffit si un paiement embarque déjà une page du site.
Rapport d’abord, et pas dans l’administration
Content-Security-Policy-Report-Only applique les mêmes règles sans rien bloquer. Le navigateur consigne les violations. Sur un site déjà en ligne, l’ordre est fixe : des rapports sur l’accueil, un article, un formulaire et l’écran de connexion, puis seulement l’en-tête bloquant. Une page de test sans extension ne prouve rien. Une rafale de violations style-src, alors que vous ne vouliez traiter que les scripts, signale une directive de trop.
L’administration n’est pas prête pour la même politique. Le ticket #59446 décrit ce manque côté cœur. Depuis la version 0.2.0, Strict CSP s’arrête au front et à wp-login.php, et se retire de l’éditeur de site. Forcer l’en-tête bloquant sur /wp-admin/ coupe l’éditeur de blocs. Une CSP limitée au site public n’est pas une politique inachevée : c’est le périmètre que WordPress tient en octobre 2026.
Trois réponses réelles suffisent avant d’annoncer une couverture : une page en cache, une page hors cache, et wp-login.php. Le nonce- de l’en-tête doit être celui des balises de la même réponse. Plusieurs en-têtes Content-Security-Policy se cumulent par intersection : une seconde politique, posée par un autre plugin ou par l’hébergeur, peut bloquer ce que la première autorisait.
Ce que l’en-tête ne fait pas
MDN le précise pour 'strict-dynamic' : si un script que vous avez autorisé fabrique une balise <script> à partir d’une donnée attaquable, la confiance se propage et la CSP ne rattrape pas l’erreur. L’échappement à l’affichage, les rôles WordPress et les mises à jour restent nécessaires. La politique ne remplace pas non plus le HTTPS, ni les décisions de vie privée déjà traitées dans l’article Cybersécurité et vie privée. Elle répond à une question étroite : quel code cette page a-t-elle le droit d’exécuter.
Questions fréquentes
Une CSP remplace-t-elle les mises à jour de WordPress ?
Non. Elle limite l’exécution d’un script non prévu lorsqu’une injection HTML existe déjà. Les correctifs du cœur, des thèmes et des extensions ferment la faille. Authentification, injection SQL et téléversement de fichiers restent hors de son champ.
Pourquoi la mise en forme disparaît-elle ?
En général, style-src vaut 'self' sans les styles inline de WordPress. 'strict-dynamic' ne concerne pas les feuilles de style. Retirez d’abord cette directive, vérifiez que les violations restantes portent sur script-src, puis traitez les styles à part.
Un cache de page peut-il coexister avec un nonce ?
Oui, si le nonce final n’est pas celui figé dans le fichier en cache. Soit le HTML n’est pas mis en cache, soit un substitut est remplacé au moment de servir l’en-tête, soit les scripts inline stables sont couverts par un hash. Un nonce identique pour tous les visiteurs, visible dans le source, ne sert plus.
Faut-il la même politique dans wp-admin ?
Non, pas dans l’état du cœur en octobre 2026. Le ticket #59446 et Strict CSP 0.3.2 limitent le blocage au site public et à l’écran de connexion. L’étendre à l’administration casse des écrans dont les scripts ne portent pas encore de nonce.
Le mode Report-Only protège-t-il les visiteurs ?
Non. Il observe seulement. Rien n’est bloqué tant que l’en-tête s’appelle Content-Security-Policy-Report-Only. Il sert à lire les violations de pages réellement visitées avant le passage au blocage.
Nonce ou hash ?
Le nonce convient aux pages construites à chaque requête. Le hash convient au HTML mis en cache dont le script inline ne change pas. Les deux peuvent coexister. Aucun ne corrige un script imprimé en dehors des API WordPress.
Aller plus loin
Ce réglage est un point de production, pas un chapitre des parcours HTML, CSS ou Sass. Ceux-ci sont sur la page Apprentissage. Les articles ponctuels sont sur le blog. Pour faire relire un en-tête déjà en place sur un site WordPress, le formulaire de la page contact suffit.
Voir Egalement
My Blog
Cybersécurité et vie privée : Préserver ses données à l’ère du numérique
6 min de lecture Dans notre monde toujours plus connecté, la cybersécurité et la vie privée ne sont plus des sujets réservés aux experts en informatique. Ils sont devenus ... Read more
Elon Musk : Tout ce qu’il faut savoir sur l’homme derrière Tesla, SpaceX et X (Twitter)
6 min de lecture Introduction Elon Musk est l’une des figures les plus influentes du XXIe siècle. Visionnaire, entrepreneur, et innovateur, il a révolutionné plusieurs industries : la mobilité ... Read more