Guide

En-têtes de sécurité WordPress : quoi ajouter, et ce qui peut casser votre site

Un scan de sécurité signale des en-têtes manquants : HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy. Ce sont des lignes que votre serveur envoie avec chaque page pour dire au navigateur avec quelle rigueur la traiter. WordPress lui-même n'en envoie presque aucune sur les pages que voient les visiteurs. Trois s'ajoutent dès aujourd'hui sans vrai risque. Les trois autres demandent un petit plan, car une mauvaise valeur peut bloquer les visiteurs ou vos propres scripts. Ce guide passe chacun en revue, indique où le définir sous WordPress et explique pourquoi des en-têtes présents le mois dernier disparaissent à nouveau.

Ce que sont les en-têtes de sécurité, et pourquoi un manquant compte

Chaque réponse de votre serveur commence par des en-têtes : le type de contenu, les règles de cache, les cookies. Les en-têtes de sécurité en sont quelques-uns de plus, qui activent des protections que le navigateur possède déjà mais n'utilise que si le site le demande. X-Frame-Options empêche un autre site de charger le vôtre dans un cadre invisible pour piéger les visiteurs et les faire cliquer (clickjacking). HSTS dit au navigateur de n'utiliser que HTTPS, même quand quelqu'un tape l'adresse sans. Content-Security-Policy indique d'où les scripts peuvent se charger, ce qui limite ce que du code injecté peut faire.

Un en-tête manquant n'est pas une faille en soi et ne veut pas dire que le site a été piraté. Il veut dire qu'une des défenses du navigateur est éteinte. C'est pour cela que les scanners le signalent comme un avertissement et non comme un échec — et pour cela qu'il vaut la peine d'ajouter les en-têtes sûrs et de planifier le reste, plutôt que d'ignorer toute la liste.

Le cœur de WordPress envoie X-Frame-Options: SAMEORIGIN (et une règle frame-ancestors équivalente) sur la page de connexion et dans wp-admin. La partie publique du site — les pages qu'un scan examine — n'en reçoit aucun des six, sauf si votre hébergeur, votre CDN ou une extension les ajoute.

Comment voir ceux que votre site envoie

Dans le navigateur : ouvrez la page d'accueil, puis Outils de développement → Réseau, rechargez, cliquez sur la première requête (la page elle-même) et lisez les en-têtes de réponse. Dans un terminal, la commande ci-dessous suit les redirections comme un navigateur et n'affiche que les six en-têtes de sécurité. Celui qui n'apparaît pas dans la sortie manque.

Vérifiez la page finale après les redirections, en HTTPS. Un en-tête envoyé par l'adresse http:// mais pas par la page https:// manque toujours pour les visiteurs.

curl -sIL https://example.com/ | grep -iE "strict-transport-security|content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy"

Les trois à ajouter dès aujourd'hui

X-Content-Type-Options: nosniff empêche le navigateur de deviner le type d'un fichier : un fichier servi comme texte n'est pas exécuté comme script, et un script ou une feuille de style avec le mauvais type de contenu est bloqué. Sur un site WordPress normal, rien ne change, car WordPress, le serveur web et les extensions bien faites envoient déjà les bons types.

X-Frame-Options: SAMEORIGIN laisse vos propres pages s'intégrer entre elles, mais interdit à tout autre site d'intégrer les vôtres. La seule chose qu'il casse est une page que vous intégrez volontairement sur un autre domaine — un formulaire de réservation sur le site d'un partenaire, par exemple. Dans ce cas, utilisez plutôt la directive frame-ancestors de Content-Security-Policy, qui accepte une liste de sites autorisés.

Referrer-Policy: strict-origin-when-cross-origin n'envoie aux autres sites que votre domaine, pas l'adresse complète de la page d'où venait le visiteur, et rien du tout de HTTPS vers HTTP. C'est aussi ce que font les navigateurs actuels quand aucune politique n'est définie ; l'ajouter rend surtout ce comportement explicite. Des six, c'est le moins risqué à laisser manquant et le plus simple à ajouter.

Sous Apache (la plupart des hébergements mutualisés WordPress), placez ces lignes dans le .htaccess au-dessus du bloc # BEGIN WordPress : WordPress ne réécrit que ce qui se trouve entre ses propres marqueurs, donc les lignes restent quand les permaliens sont enregistrés. La ligne Permissions-Policy est expliquée plus bas ; laissez-la de côté tant que vous n'avez pas lu cette partie.

<IfModule mod_headers.c>
  Header set X-Content-Type-Options "nosniff"
  Header set X-Frame-Options "SAMEORIGIN"
  Header set Referrer-Policy "strict-origin-when-cross-origin"
  Header set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

HSTS : seulement quand HTTPS fonctionne partout

Strict-Transport-Security fait retenir au navigateur, pendant max-age secondes, que le site est uniquement en HTTPS. Il supprime la première requête HTTP non chiffrée — celle qu'un attaquant sur un Wi-Fi public peut intercepter — et transforme les avertissements de certificat en erreurs que les visiteurs ne peuvent pas contourner. Les navigateurs ignorent l'en-tête s'il arrive en HTTP simple ; il ne fonctionne donc qu'à partir d'une page HTTPS.

Le risque, c'est cette mémoire. Si le certificat expire ou qu'un sous-domaine tourne encore en HTTP, les navigateurs qui ont vu l'en-tête refusent de le charger jusqu'à la fin de max-age, et vous ne pouvez le désactiver (max-age=0) que pour les navigateurs qui reviennent sur le site en HTTPS. Déployez-le donc par étapes : commencez par max-age=300 (cinq minutes), vérifiez toutes les pages et tous les sous-domaines, puis montez à un jour, une semaine et enfin 31536000 (un an). N'ajoutez includeSubDomains que lorsque chaque sous-domaine — mail, boutique, préproduction — sert du HTTPS.

La directive preload est une décision à part. Elle demande aux navigateurs d'inscrire votre domaine dans leur liste intégrée de sites HTTPS uniquement ; il faut un an de max-age plus includeSubDomains, et sortir de la liste prend longtemps. Laissez-la de côté si vous n'êtes pas sûr. Si le site est derrière Cloudflare, HSTS est un réglage sous SSL/TLS → Edge Certificates plutôt qu'une ligne côté serveur.

Content-Security-Policy : commencez en mode report-only

Une Content-Security-Policy complète liste chaque origine depuis laquelle scripts, styles, polices, images et cadres peuvent se charger. C'est la plus forte des six contre le code injecté, et celle qui risque le plus de casser un site WordPress : thèmes, constructeurs de pages, statistiques, bandeaux de consentement, widgets de chat et boutons de paiement chargent tous des scripts depuis quelque part, souvent en ligne. Copier une politique stricte depuis un tutoriel en bloquera certains.

Envoyez-la d'abord en Content-Security-Policy-Report-Only. Le navigateur n'applique rien et note chaque violation dans la console : vous pouvez parcourir le site — le paiement aussi — et ajouter chaque origine dont vous avez vraiment besoin. Ne renommez l'en-tête en Content-Security-Policy que lorsque la console reste silencieuse.

Une politique courte qui casse rarement quoi que ce soit est une première étape raisonnable pendant que vous travaillez à la complète : object-src 'none'; base-uri 'self'; frame-ancestors 'self'. Elle bloque les anciennes intégrations d'extensions, empêche le code injecté de changer la cible des liens relatifs et fait le même travail que X-Frame-Options: SAMEORIGIN.

Définissez-la comme en-tête. Une politique dans une balise <meta http-equiv> fonctionne pour la plupart des directives, mais frame-ancestors y est ignoré, et les outils qui lisent les en-têtes de réponse ne la voient pas.

Permissions-Policy : éteignez ce que vous n'utilisez pas

Permissions-Policy décide quelles fonctions du navigateur — caméra, micro, localisation et d'autres — la page et tout ce qu'elle intègre peuvent demander. camera=(), microphone=(), geolocation=() éteint ces trois-là sur tout le site, si bien qu'un script injecté dans une page ou une publicité intégrée ne peut pas les demander aux visiteurs.

N'indiquez que les fonctions dont vous savez que le site ne se sert pas. Un localisateur de magasins qui demande la position du visiteur a besoin de geolocation=(self) au lieu de geolocation=() ; une extension d'appel vidéo ou d'enregistrement a besoin de la caméra et du micro. Vérifiez les pages qui utilisent ces fonctions après l'avoir ajouté.

Où les définir sous WordPress

Il y a trois endroits, et plus on est haut, plus on couvre. Le serveur web (.htaccess sous Apache, le bloc server sous Nginx) couvre chaque réponse, y compris images, scripts et pages en cache. Un CDN devant le site couvre tout ce qui passe par lui. À l'intérieur de WordPress — une extension de sécurité, quelques lignes sur le hook send_headers ou "Add safe security headers" de Relvato — seules les pages que WordPress construit lui-même sont couvertes ; une page servie par un cache avant que WordPress ait fini de charger peut partir sans eux. C'est très bien comme correctif rapide, mais vérifiez le résultat sur la page d'accueil en ligne.

Sous Nginx, utilisez add_header avec always, pour que l'en-tête soit aussi envoyé sur les pages d'erreur. Attention à un piège : un bloc location qui a son propre add_header n'hérite pas de ceux du bloc server. Quelqu'un ajoute un en-tête de cache à une location, et les en-têtes de sécurité en disparaissent sans bruit.

Définissez chaque en-tête à un seul endroit. Deux couches qui ajoutent le même en-tête l'envoient deux fois, et deux en-têtes Content-Security-Policy sont appliqués tous les deux : la politique effective est alors plus stricte que chacune d'elles.

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Pourquoi les en-têtes disparaissent à nouveau

Les en-têtes sont rarement retirés exprès. Ils disparaissent quand l'endroit qui les définissait change : un déménagement chez un nouvel hébergeur (Nginx ne lit pas du tout le .htaccess, donc toutes ses règles cessent de s'appliquer), un nouveau CDN ou un réglage CDN réinitialisé, une extension de cache remplacée par une autre qui sert les pages avant le chargement de WordPress, une extension de sécurité désactivée ou une de ses options réinitialisée par une mise à jour, ou un nouveau bloc location Nginx qui n'en hérite plus.

Rien de tout cela ne produit d'erreur. Le site a le même aspect, donc la seule façon de s'en rendre compte est de revérifier les en-têtes après chaque changement — exactement ce que personne ne fait à la main après chaque mise à jour.

Comment Relvato le détecte

Le monitor Exposure scan de Relvato récupère votre page d'accueil comme un navigateur, en suivant les redirections, et lit les en-têtes de réponse. Il fonctionne sur n'importe quel site, pas seulement WordPress. Chaque en-tête manquant est un avertissement distinct — "Missing security header: HSTS", "Missing security header: Content-Security-Policy", etc. — sur une exécution par ailleurs réussie. Comme chacun est séparé, vous pouvez cliquer sur "Ignore this warning" pour un en-tête que vous avez écarté pour l'instant, comme CSP, et continuer à être prévenu pour les autres. Il ne lit que les en-têtes ; une politique dans une balise meta ne compte pas.

Sous WordPress avec l'extension Relvato, les trois en-têtes sûrs ont un bouton "Fix in WordPress" à côté de l'avertissement. Dans WordPress → Relvato, un clic active "Add safe security headers" : X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin et X-Frame-Options: SAMEORIGIN, chacun seulement si le site ne l'envoie pas déjà. "Switch off" le désactive au même endroit. HSTS, Content-Security-Policy et Permissions-Policy ne sont jamais ajoutés pour vous, car une mauvaise valeur casse des sites — voir ce que Relvato ne corrige pas pour vous.

Avec l'offre Free, vous cliquez sur Run quand vous voulez lancer le scan, ou vous le planifiez chaque semaine. Pro relance vos monitors tout seul après chaque mise à jour : un en-tête qu'une mise à jour d'extension ou un changement d'hébergeur a fait disparaître réapparaît en avertissement sans que personne n'ait à penser à vérifier.

Les six en-têtes de sécurité en un coup d'œil

En-têteCe qu'il faitUne valeur sûre pour commencerPeut-il casser quelque chose ?
X-Content-Type-OptionsEmpêche le navigateur de deviner les types de fichiersnosniffPresque jamais sous WordPress
X-Frame-OptionsEmpêche d'autres sites d'intégrer le vôtre (clickjacking)SAMEORIGINSeulement les pages que vous intégrez sur d'autres domaines
Referrer-PolicyLimite ce que les autres sites apprennent de la page d'où vient le visiteurstrict-origin-when-cross-originNon — c'est le comportement par défaut du navigateur
Permissions-PolicyÉteint caméra, micro, localisation et d'autres fonctionscamera=(), microphone=(), geolocation=()Oui, si une page utilise l'une de ces fonctions
Strict-Transport-Security (HSTS)Fait utiliser uniquement HTTPS aux navigateursmax-age=300, augmenté par étapesOui — bloque les sous-domaines HTTP et les certificats expirés
Content-Security-PolicyIndique d'où scripts, styles et cadres peuvent se chargerD'abord Report-Only ; object-src 'none'; base-uri 'self'; frame-ancestors 'self'Oui — le plus susceptible de bloquer vos propres scripts

Questions fréquentes

Quel en-tête de sécurité ajouter en premier ?

X-Content-Type-Options, X-Frame-Options et Referrer-Policy, ensemble : ils sont sûrs sur presque tous les sites WordPress et tiennent en quelques lignes. Puis Permissions-Policy pour les fonctions que vous n'utilisez pas, puis HSTS par étapes, et Content-Security-Policy en dernier, d'abord en mode report-only.

Les en-têtes de sécurité aident-ils le SEO ?

Google ne cite pas les en-têtes de sécurité parmi ses signaux de classement ; HTTPS en est un. HSTS aide en gardant les visiteurs en HTTPS. La vraie raison de les ajouter est de protéger les visiteurs, pas le classement.

Une extension peut-elle les ajouter pour moi ?

Oui, et plusieurs extensions de sécurité le font. Une extension ne les ajoute qu'aux pages que WordPress construit ; vérifiez ensuite la page d'accueil en ligne — un cache de pages ou un CDN peut servir des pages sans eux. Définissez chaque en-tête à un seul endroit pour qu'il ne soit pas envoyé deux fois.

J'ai ajouté une balise meta CSP. Pourquoi l'en-tête est-il toujours signalé manquant ?

Le scan lit les en-têtes de réponse, et une politique en <meta http-equiv> n'en est pas un. Les navigateurs appliquent la plupart des directives de la balise meta, mais pas frame-ancestors. Envoyée comme en-tête, la politique couvre tout.

Un en-tête de sécurité manquant veut-il dire que mon site a été piraté ?

Non. Il veut dire qu'une protection du navigateur est éteinte. Si vous craignez une intrusion, les signes sont différents — voir la checklist WordPress piraté.

Sources

À lire aussi
Guide

Sachez le jour même qu'un en-tête a disparu.

L'Exposure scan de Relvato vérifie vos en-têtes avec tout ce qu'un site laisse voir, et sous WordPress propose les en-têtes sûrs, ajoutés en un clic dans WordPress → Relvato — avec Undo.