Durcissement

Comment configurer les en-têtes de sécurité HTTP sur WordPress : CSP, HSTS et les autres

en-têtes de sécurité HTTP pour WordPress, CSP et HSTS

Les en-têtes de sécurité HTTP sont quelques lignes envoyées avec chaque page. Ils ne réparent pas une extension vulnérable, mais ils demandent au navigateur de vos visiteurs de refuser certains comportements dangereux : chargement en HTTP, affichage dans un cadre piégé, exécution d’un script venu d’ailleurs. Cet article explique ce que fait chaque en-tête, ce que WordPress envoie déjà, les valeurs de départ raisonnables et la méthode pour les tester sans casser l’administration.

Ce que ces en-têtes protègent, et ce qu’ils ne protègent pas

Un en-tête de sécurité est une consigne : le serveur l’envoie, le navigateur l’applique. La protection porte donc sur le visiteur et sur la façon dont vos pages sont affichées. Un robot qui attaque directement wp-login.php ou une extension ignore totalement ces consignes.

  • Ils limitent l’impact d’une faille XSS, d’un détournement de clic (clickjacking) ou d’une interception sur un Wi-Fi public.
  • Ils ne bloquent pas une injection SQL, un téléversement de fichier malveillant ou une élévation de privilèges dans une extension : seule la mise à jour corrige ces failles.
  • Ils s’ajoutent aux autres couches (mises à jour, double authentification, sauvegardes), sans les remplacer.
Schéma associant six en-têtes de sécurité HTTP aux risques qu’ils limitent, avec une pastille verte ou orange selon le risque de casse
Les six en-têtes utiles et le risque que chacun réduit. Les pastilles orange demandent une phase de test.

Ce que WordPress envoie déjà

WordPress pose quelques en-têtes lui-même, mais seulement sur certaines réponses. Les connaître évite d’en ajouter en double, avec des valeurs contradictoires.

RéponseEn-tête envoyé par WordPressDepuis
Administration (admin_init) et page de connexion (login_init)X-Frame-Options: SAMEORIGINWordPress 3.1.3
Administration et page de connexionContent-Security-Policy: frame-ancestors 'self';WordPress 6.9
Administration et page de connexionReferrer-Policy: strict-origin-when-cross-originWordPress 4.9 (valeur actuelle depuis 4.9.5)
API REST et admin-ajax.phpX-Content-Type-Options: nosniffPrésent dans le code actuel
Pages publiques du siteAucun de ces en-têtes par défaut
Fonctions send_frame_options_header() et wp_admin_headers() du cœur, vérifiées dans le code source de WordPress 6.9.

Autrement dit, ce sont vos pages publiques qui restent nues. C’est là qu’il faut agir.

Les six en-têtes à connaître, avec des valeurs de départ

En-têteRôleValeur de départRisque de casse
Strict-Transport-SecurityImpose HTTPS pour les visites suivantesmax-age=31536000 après une période d’essai courteFaible si tout le site est en HTTPS, fort si un sous-domaine ne l’est pas
X-Content-Type-OptionsInterdit au navigateur de deviner le type d’un fichiernosniffTrès faible
X-Frame-OptionsInterdit l’affichage dans un cadre d’un autre siteSAMEORIGINFaible ; DENY casserait l’aperçu de l’outil de personnalisation
Referrer-PolicyLimite l’URL transmise aux sites liésstrict-origin-when-cross-originTrès faible
Permissions-PolicyCoupe les API sensibles du navigateurcamera=(), microphone=(), geolocation=()Moyen si une extension utilise la géolocalisation ou la caméra
Content-Security-PolicyListe les sources autorisées (scripts, styles, cadres…)D’abord en mode rapport seulÉlevé : à construire site par site

HSTS : puissant, mais difficile à annuler

Strict-Transport-Security indique au navigateur de n’utiliser que HTTPS pendant max-age secondes. Le navigateur ignore cet en-tête s’il arrive en HTTP, et la toute première visite reste exposée tant qu’il ne l’a jamais reçu. Pour désactiver HSTS, il faut envoyer max-age=0 en HTTPS : impossible si votre certificat a expiré entre-temps.

Séquence HSTS entre un navigateur et un site WordPress : requête HTTP, redirection 301, requête HTTPS puis réponse avec l’en-tête Strict-Transport-Security
Seule la première requête en HTTP reste exposée. Les visites suivantes passent directement en HTTPS.

La liste de préchargement hstspreload.org supprime ce point faible, avec des conditions strictes : certificat valide, redirection de HTTP vers HTTPS sur le même hôte, tous les sous-domaines en HTTPS, et un en-tête avec max-age d’au moins 31536000 secondes (un an), includeSubDomains et preload. Le site prévient que l’inscription ne s’annule pas facilement et que le retrait met des mois à atteindre les utilisateurs. Ne demandez le préchargement que si tous vos sous-domaines (messagerie web, préproduction, ancien extranet) sont en HTTPS pour longtemps.

X-Frame-Options et frame-ancestors

Les deux empêchent qu’un autre site affiche le vôtre dans un cadre invisible pour vous faire cliquer à votre insu. OWASP recommande la directive CSP frame-ancestors, qui remplace X-Frame-Options dans les navigateurs qui la prennent en charge. Envoyer les deux avec SAMEORIGIN et 'self' reproduit ce que WordPress fait déjà pour l’administration depuis la version 6.9.

Content-Security-Policy : commencer en mode rapport

La CSP est l’en-tête le plus efficace contre les scripts injectés, et le plus délicat sur WordPress : le cœur, les thèmes et beaucoup d’extensions écrivent des scripts en ligne. Une politique stricte copiée d’un tutoriel bloque souvent l’éditeur, les formulaires ou les statistiques. La bonne méthode :

  1. Envoyer Content-Security-Policy-Report-Only : le navigateur signale les violations sans rien bloquer.
  2. Ouvrir la console des outils de développement sur l’accueil, un article, le panier, l’éditeur, et noter chaque source bloquée.
  3. Pour collecter les rapports à distance, ajouter report-to et l’en-tête Reporting-Endpoints : sans destination, aucun rapport n’est envoyé.
  4. Passer à Content-Security-Policy seulement quand la console reste propre sur tous les parcours importants.

Depuis WordPress 5.7, les fonctions wp_get_script_tag(), wp_get_inline_script_tag() et le filtre wp_script_attributes permettent d’ajouter un attribut nonce aux scripts chargés proprement. Les scripts écrits directement dans le HTML par un thème ou une extension n’en profitent pas : c’est pourquoi beaucoup de politiques WordPress conservent 'unsafe-inline' au début, ce qui affaiblit la protection contre les XSS.

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'

Ce point de départ est volontairement permissif : il sert à mesurer, pas à protéger. Resserrez-le ensuite source par source (domaine des polices, outil de statistiques, passerelle de paiement).

Les en-têtes à ne plus ajouter

Beaucoup de guides anciens et d’extensions proposent encore des en-têtes abandonnés. Les ajouter ne protège plus rien, et l’un d’eux peut même nuire.

En-têteStatut actuelQue faire
X-XSS-ProtectionDéprécié et non standard (MDN). Peut créer des failles XSS sur des sites sains.Ne pas l’envoyer, ou l’envoyer à 0. Utiliser une CSP.
Expect-CTDéconseillé par OWASP, qui recommande de le retirer.Supprimer.
Feature-PolicyRemplacé par Permissions-Policy, à la syntaxe différente.Convertir, par exemple camera=().
X-Powered-By et Server détailléPas des en-têtes de sécurité : ils annoncent la version de PHP ou du serveur.Retirer (expose_php = Off dans PHP) ou neutraliser.

Où les configurer : serveur, PHP ou extension

Le choix dépend de votre hébergement. La règle générale : plus la consigne est posée tôt, plus elle couvre de réponses.

  • Au niveau du serveur (.htaccess Apache ou LiteSpeed, configuration Nginx) : couvre aussi les fichiers statiques, les erreurs et les pages servies par un cache de pages sans passer par PHP.
  • En PHP (action send_headers) : pratique sur un hébergement sans accès à la configuration, mais absent des réponses qui ne chargent pas WordPress, comme une page servie directement par un cache.
  • Par une extension : même limite que PHP, avec une interface. Vérifiez qu’elle n’ajoute pas les en-têtes dépréciés vus plus haut.

Apache ou LiteSpeed (.htaccess)

Avec mod_headers, la condition always ajoute l’en-tête aussi sur les réponses d’erreur et les redirections, ce qui n’est pas le cas de la condition par défaut.

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

Nginx

Sans le paramètre always (disponible depuis Nginx 1.7.5), add_header ne s’applique qu’aux codes 200, 201, 204, 206, 301, 302, 303, 304, 307 et 308. Autre piège : un bloc location qui définit son propre add_header n’hérite plus des en-têtes du niveau server. Nginx 1.29.3 a ajouté add_header_inherit merge; pour changer ce comportement.

add_header Strict-Transport-Security "max-age=31536000" always;
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;

En PHP, dans une extension maison

add_action( 'send_headers', function () {
    header( 'X-Content-Type-Options: nosniff' );
    header( 'Referrer-Policy: strict-origin-when-cross-origin' );
    header( 'Permissions-Policy: camera=(), microphone=(), geolocation=()' );
} );

N’ajoutez pas HSTS en PHP si votre hébergeur ou votre CDN le pose déjà : deux valeurs différentes compliquent le diagnostic.

Tester sans casser le site

  1. En ligne de commande : curl -I https://votre-site.fr/ affiche les en-têtes réellement reçus. Testez aussi une page 404, une image de wp-content/uploads et wp-login.php.
  2. Dans le navigateur : onglet Réseau des outils de développement, puis la réponse du document principal.
  3. Avec le HTTP Observatory de Mozilla, intégré à MDN depuis juillet 2024, qui note la configuration et explique chaque point.
  4. Parcourez ensuite les fonctions sensibles : éditeur de blocs, outil de personnalisation, formulaires, paiement, intégrations vidéo.
  5. Videz tous les caches (extension, hébergeur, CDN) avant chaque test : une page en cache peut conserver les anciens en-têtes.
Conférence en français : tour d’horizon des en-têtes de sécurité (CSP, HSTS, X-Frame-Options, COxP) et des attaques qu’ils limitent.
Conférence AFUP consacrée à la mise en place d’une Content Security Policy dans une application PHP.

Avec WP Masquerade

L’extension gratuite ajoute déjà X-Content-Type-Options: nosniff dès le niveau 1 du module Durcissement. WP Masquerade Pro gère les en-têtes de sécurité depuis l’administration (HSTS, X-Frame-Options, Referrer-Policy, CSP…), sans modifier les fichiers du serveur. Comme pour toute solution en PHP, une page servie par un cache en amont peut ne pas porter ces en-têtes : vérifiez le résultat avec curl et, si besoin, consultez la page compatibilité hébergeurs et caches. Le comparatif détaille la frontière entre les versions gratuite et Pro.

Une plateforme de consentement aux cookies hébergée charge son script depuis son propre domaine, qu’il faut alors autoriser dans la CSP ; notre comparatif des plugins cookies RGPD indique quelles solutions restent sur votre serveur.

À retenir

  • Les en-têtes de sécurité protègent vos visiteurs, pas votre code : ils complètent les mises à jour sans les remplacer.
  • WordPress protège déjà l’administration et la connexion contre l’affichage en cadre ; vos pages publiques n’ont rien par défaut.
  • Déployez d’abord nosniff, SAMEORIGIN, Referrer-Policy et Permissions-Policy, qui cassent rarement quelque chose.
  • Montez HSTS par paliers et ne demandez le préchargement que si tous les sous-domaines sont en HTTPS.
  • Construisez la CSP en mode rapport seul, page par page, avant de l’appliquer.
  • N’envoyez plus X-XSS-Protection, Expect-CT ni Feature-Policy.

Sources

Autres sources consultées : documentation officielle.

La sécurité WordPress, sans le bruit

Un e-mail quand ça compte : nouvelles attaques observées, mises à jour de l’extension et guides pratiques. Pas de spam, désinscription en un clic.

Commencez gratuitement, passez à Pro quand vous êtes prêt

La version gratuite masque déjà WordPress et déplace la page de connexion. Pro ajoute alias, double authentification et surveillance.