Chapitre 2.4 — En-têtes de sécurité
⏱️ TL;DR — Le serveur peut envoyer au navigateur des en-têtes de sécurité qui durcissent son comportement. Les essentiels :
Content-Security-Policy(CSP) — restreint d’où le navigateur peut charger scripts, styles, etc. : le meilleur filet anti-XSS ;Strict-Transport-Security(HSTS) — force le HTTPS pour les prochaines visites ;X-Content-Type-Options: nosniff— empêche le navigateur de « deviner » les types MIME ;X-Frame-Options/frame-ancestors— anti-clickjacking ;Referrer-Policy— limite la fuite d’URL ;Permissions-Policy— coupe les API sensibles (caméra, géoloc). Gratuits à poser, ils ajoutent des couches de defense in depth.
🎯 Objectifs
- Savoir ce que fait chaque en-tête de sécurité majeur et contre quoi il protège.
- Écrire une CSP de base et comprendre pourquoi elle est un filet (pas un substitut à l’encodage).
- Activer HSTS en connaissant ses implications (durée,
preload). - Poser les en-têtes anti-clickjacking,
nosniffetReferrer-Policy.
L’idée : donner des consignes de sécurité au navigateur
Le navigateur est puissant : il exécute du JS, charge des ressources, encapsule des pages dans des iframes, envoie des Referer. Par défaut, il est permissif. Les en-têtes de sécurité sont des consignes que le serveur lui envoie pour restreindre ces capacités — et donc réduire ce qu’un attaquant peut faire même s’il trouve une brèche. Ils s’ajoutent aux défenses applicatives : ce sont des couches (defense in depth), pas des solutions uniques.
Content-Security-Policy (CSP) — le filet anti-XSS
La CSP est le plus puissant. Elle déclare au navigateur quelles sources sont autorisées pour chaque type de ressource (scripts, styles, images, iframes, connexions…). Tout ce qui n’est pas autorisé est bloqué. Son intérêt majeur : si un XSS parvient à injecter un <script> ou un onerror=..., une CSP bien faite empêche son exécution.
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'Lecture : default-src 'self' (par défaut, ne charger que depuis ma propre origine), script-src 'self' (scripts uniquement depuis mon origine — donc pas de script inline ni distant non autorisé), object-src 'none' (pas de plugins), frame-ancestors 'self' (anti-clickjacking, voir plus bas).
Le point qui fait la force et la difficulté de la CSP : par défaut, une CSP stricte interdit le JavaScript inline (<script>...</script> et les gestionnaires onclick=). C’est justement ce qui bloque le XSS injecté — mais ça oblige à sortir ton propre JS dans des fichiers, ou à utiliser des nonces/hashes pour autoriser explicitement tes scripts inline légitimes.
💡 Réflexe — La CSP est un filet, pas un trampoline : elle rattrape les XSS qui passeraient malgré l’encodage de sortie, elle ne le remplace pas. On garde l’auto-échappement (React, Twig) et l’encodage comme défense principale, et la CSP en plus. On la déploie souvent d’abord en mode
Content-Security-Policy-Report-Only(elle signale les violations sans bloquer) pour la régler sans casser le site, puis on l’applique. Détails de rédaction en Partie 7.
⚠️ Piège — Une CSP truffée de
'unsafe-inline'et'unsafe-eval'surscript-srcne protège quasiment plus contre le XSS :'unsafe-inline'réautorise précisément le script inline que l’attaquant veut injecter. Une CSP existe surtout pour interdire l’inline non prévu — la remplir d’unsafe-*revient à mettre un filet troué. Utilise des nonces ou hashes pour tes scripts légitimes plutôt qu’unsafe-inline.
Strict-Transport-Security (HSTS) — forcer HTTPS
HSTS dit au navigateur : « pour ce domaine, n’utilise que HTTPS pendant la durée indiquée, même si l’utilisateur tape http:// ou clique un lien en clair ». Ça ferme la fenêtre où une toute première requête en HTTP pourrait être interceptée et détournée.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadmax-age: durée (en secondes) pendant laquelle le navigateur mémorise l’obligation HTTPS (ici ~2 ans).includeSubDomains: étend la règle à tous les sous-domaines (à n’activer que si tous sont en HTTPS).preload: permet l’inscription dans une liste intégrée aux navigateurs (le domaine est alors forcé en HTTPS dès la première visite). Engagement fort et difficile à annuler — à activer en connaissance de cause.
⚠️ Piège — N’active
includeSubDomainset surtoutpreloadque si tous tes sous-domaines sont (et resteront) en HTTPS. Lepreloadest durable et pénible à retirer : un sous-domaine oublié en HTTP deviendra inaccessible pour les navigateurs qui ont mémorisé la règle. Commence avec unmax-agecourt, vérifie, puis augmente.
X-Content-Type-Options: nosniff
Par défaut, certains navigateurs « devinent » le type d’un fichier (MIME sniffing) au lieu de croire l’en-tête Content-Type. Un attaquant peut exploiter ça pour faire exécuter comme script un fichier uploadé annoncé comme image. nosniff désactive cette devinette : le navigateur respecte le Content-Type déclaré.
X-Content-Type-Options: nosniffAnti-clickjacking : X-Frame-Options / frame-ancestors
Le clickjacking consiste à charger ton site dans une iframe invisible par-dessus un leurre, pour piéger l’utilisateur en lui faisant cliquer sur ton interface sans le savoir (voir Partie 8). La parade : interdire que ton site soit mis en iframe par d’autres origines.
- L’ancien en-tête :
X-Frame-Options: DENY(jamais en iframe) ouSAMEORIGIN(seulement par ta propre origine). - Le moderne, via CSP :
frame-ancestors 'none'(équivalentDENY) ouframe-ancestors 'self'.
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'Referrer-Policy et Permissions-Policy
Referrer-Policycontrôle ce que le navigateur met dans l’en-têteRefereren quittant ton site. Sans réglage, une URL sensible (https://app.formacampus.fr/reset?token=SECRET) pourrait fuiter vers un site tiers. Une valeur prudente :strict-origin-when-cross-origin(n’envoyer que l’origine, pas le chemin, en cross-site).
Referrer-Policy: strict-origin-when-cross-originPermissions-Policypermet de couper des API puissantes du navigateur (caméra, micro, géolocalisation…) que ton site n’utilise pas, réduisant la surface d’abus.
Permissions-Policy: geolocation=(), camera=(), microphone=()Où poser ces en-têtes ?
Selon ta stack : dans le reverse proxy (Nginx — voir Serveur Linux), dans le framework (next.config pour Next.js — Partie 10 ; un bundle comme NelmioSecurityBundle pour Symfony — Partie 9), ou via un middleware. L’important est qu’ils soient posés partout, de façon cohérente. Des outils comme securityheaders.com ou l’audit Lighthouse aident à vérifier.
📚 La source — Pour la liste à jour et la syntaxe exacte, les références sont MDN (chaque en-tête a sa page) et l’OWASP Secure Headers Project. Les directives CSP en particulier sont nombreuses et évoluent : on pose ici les essentielles, on affine avec MDN au moment d’écrire la politique.
🧭 Sur FormaCampus — Le reverse proxy de FormaCampus pose un jeu d’en-têtes cohérent sur toutes les réponses :
HSTS(avecincludeSubDomains, tous les sous-domaines étant en HTTPS),nosniff,X-Frame-Options: DENY+frame-ancestors 'none'(la plateforme n’a aucune raison d’être mise en iframe),Referrer-Policy: strict-origin-when-cross-origin(pour ne pas fuiter les tokens de reset dans leReferer), et une CSP stricte avec nonces sur les scripts, déployée d’abord enReport-Only. Ces en-têtes ne remplacent pas les défenses applicatives — ils forment le filet par-dessus.
✏️ Exercices
Exercice 1 — Lis la CSP. Que fait cette politique, et quelle faiblesse contient-elle ? Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'.
✅ Solution
Elle limite par défaut les ressources à l’origine propre (default-src 'self') — bien. Mais script-src 'self' 'unsafe-inline' réautorise le JavaScript inline, ce qui annule l’essentiel de la protection anti-XSS : un attaquant qui injecte <script>...</script> ou onerror=... verra son code exécuté, puisque l’inline est permis. Correctif : retirer 'unsafe-inline' et autoriser les scripts inline légitimes via un nonce (script-src 'self' 'nonce-XXXX') ou un hash.
Exercice 2 — Associe l’en-tête à la menace. Relie chaque en-tête à ce qu’il atténue : (a) HSTS ; (b) X-Frame-Options: DENY ; (c) nosniff ; (d) Referrer-Policy. Menaces : interception d’une 1re requête HTTP · clickjacking · exécution d’un upload « deviné » comme script · fuite d’un token dans l’URL.
✅ Solution
(a) HSTS → interception d’une première requête HTTP (force HTTPS). (b) X-Frame-Options: DENY → clickjacking (interdit la mise en iframe). (c) nosniff → exécution d’un upload deviné comme script (empêche le MIME sniffing). (d) Referrer-Policy → fuite d’un token dans l’URL via l’en-tête Referer vers des sites tiers.
🧠 Quiz de révision
1. Que fait la CSP et pourquoi est-elle un « filet » anti-XSS ?
Elle déclare quelles sources le navigateur peut charger (scripts, styles…) et bloque le reste, notamment le JavaScript inline non autorisé. Si un XSS injecte un script, une CSP stricte empêche son exécution. C’est un filet en plus de l’encodage de sortie, pas un remplacement.
2. À quoi sert HSTS et quelle précaution prendre avec preload ?
preload ?HSTS force le navigateur à n’utiliser que HTTPS pour le domaine pendant max-age, fermant la fenêtre d’interception d’une première requête HTTP. Précaution : includeSubDomains/preload ne s’activent que si tous les sous-domaines sont en HTTPS — le preload est durable et difficile à retirer.
3. Que fait X-Content-Type-Options: nosniff ?
X-Content-Type-Options: nosniff ?Il empêche le navigateur de deviner le type MIME d’un fichier : il respecte le Content-Type déclaré. Cela évite qu’un fichier (ex. un upload annoncé « image ») soit interprété et exécuté comme script.
4. Quel(s) en-tête(s) protège(nt) du clickjacking ?
X-Frame-Options: DENY (ou SAMEORIGIN) et, en version moderne, la directive CSP frame-ancestors 'none' (ou 'self'). Ils interdisent que ton site soit chargé dans une iframe par d’autres origines.
5. Ces en-têtes remplacent-ils les défenses applicatives ?
Non : ce sont des couches supplémentaires (defense in depth). La CSP ne remplace pas l’encodage de sortie, HSTS ne remplace pas HTTPS bien configuré, frame-ancestors ne remplace pas la logique métier. Ils durcissent le navigateur en complément du code sécurisé.
Fin de la Partie 2. Le terrain — HTTP, origines, cookies, TLS, en-têtes — est posé. Direction la Partie 3 — Cryptographie pour devs : hacher, chiffrer, signer, générer de l’aléatoire — sans jamais rouler sa propre crypto.