cyber / cybersecurite-fondamentale
Sécurité web : headers de sécurité et CSP
Explication
Ce que vous allez apprendre
- Comprendre que les headers de sécurité sont des instructions données au navigateur, pas au serveur
- Configurer HSTS, X-Frame-Options et X-Content-Type-Options pour couvrir les cas les plus courants
- Écrire une Content-Security-Policy restrictive pour limiter l'impact d'un XSS résiduel
- Expliquer pourquoi
unsafe-inlineaffaiblit fortement une CSP et comment utiliser un nonce à la place - Déployer une CSP progressivement grâce au mode Report-Only avant de l'activer en mode strict
Dans quel contexte ?
Un site e-commerce est victime d'un XSS stored malgré l'échappement en place, à cause d'une bibliothèque tierce mal maintenue. Sans Content-Security-Policy, le script injecté s'exécute librement et exfiltre les données de paiement. Avec une CSP stricte limitant les scripts au seul domaine du site, le navigateur refuse d'exécuter le script injecté, même si l'injection elle-même n'a pas pu être évitée en amont. C'est exactement ce rôle de filet de sécurité que joue la CSP.
Des instructions données au navigateur, pas au serveur
Les headers de sécurité HTTP sont des instructions que le serveur envoie au navigateur pour lui dire comment se comporter face à CE site précis. Ce ne sont pas des protections magiques mais des règles que le navigateur s'engage à respecter — un peu comme des consignes de sécurité affichées à l'entrée d'un bâtiment que les visiteurs (ici, le navigateur) suivent volontairement.
Trois headers simples à fort impact
| Header | Ce qu'il empêche | Effet concret |
|---|---|---|
| Strict-Transport-Security (HSTS) | Le downgrade HTTPS -> HTTP | Force le navigateur à toujours utiliser HTTPS pour ce domaine |
| X-Frame-Options | Le clickjacking | Empêche le site d'être chargé dans une iframe invisible sur un autre site |
| X-Content-Type-Options: nosniff | Le MIME sniffing | Empêche le navigateur de "deviner" un type de fichier différent de celui annoncé |
La CSP, la défense la plus puissante de cette leçon
La Content-Security-Policy va plus loin : elle dit explicitement au navigateur "n'exécute JAMAIS de script qui ne vient pas de ces sources précises". C'est un filet de sécurité redoutable contre le XSS résiduel vu en leçon 9 : même si un attaquant parvient à injecter un <script> dans la page, le navigateur refusera de l'exécuter s'il ne respecte pas la politique définie.
Piège fréquent
Autoriser les scripts inline (unsafe-inline) dans la CSP annule une bonne partie de sa protection contre le XSS, puisque c'est justement le type de code qu'un attaquant injecterait. La bonne pratique est d'utiliser un "nonce" unique généré à chaque requête, que seul le serveur légitime connaît et peut associer aux scripts autorisés.
Une démarche prudente pour déployer une CSP
Bonne pratique
Le mode Content-Security-Policy-Report-Only permet d'observer ce qui SERAIT bloqué sans réellement bloquer, pour ajuster la politique avant de l'activer en mode strict — évite de casser le site en production par surprise.
Commandes & code
Sécurité web : headers de sécurité et CSP
Les headers HTTP de sécurité indiquent au navigateur comment se comporter de manière défensive vis-à-vis d'un site donné.
# Panorama des headers de sécurité essentiels
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Content-Security-Policy: default-src 'self'# Rôle de chaque header
HSTS : force HTTPS pour toutes les requêtes futures, empêche le SSL stripping
X-Content-Type-Options: empêche le navigateur de "deviner" un type MIME différent (anti-XSS via upload)
X-Frame-Options : empêche le site d'être chargé dans une <iframe> (anti-clickjacking)
Referrer-Policy : limite les informations envoyées dans l'en-tête Referer vers d'autres sites
Permissions-Policy : désactive les API navigateur sensibles non utilisées (caméra, géoloc...)# Middleware FastAPI ajoutant les headers de sécurité sur toutes les réponses
from starlette.middleware.base import BaseHTTPMiddleware
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains; preload"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "geolocation=(), camera=(), microphone=()"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; connect-src 'self' https://api.technologik.local; "
"object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
)
return response
app.add_middleware(SecurityHeadersMiddleware)# Anatomie d'une Content-Security-Policy (CSP)
default-src 'self' : source par défaut pour toute ressource non explicitement listée
script-src 'self' : scripts uniquement depuis le même domaine (bloque les scripts inline sans nonce)
style-src 'self' 'unsafe-inline' : styles depuis le domaine + inline autorisé (à éviter si possible)
img-src 'self' data: https: : images locales, data URIs et toute source HTTPS
connect-src 'self' https://api.technologik.local : XHR/fetch/WebSocket limités à ces origines
object-src 'none' : bloque <object>/<embed>/<applet> (vecteurs Flash historiques)
frame-ancestors 'none' : équivalent moderne de X-Frame-Options, dans la CSP elle-même<!-- CSP avec nonce pour autoriser des scripts inline spécifiques sans "unsafe-inline" global -->
<!-- Le nonce est généré aléatoirement à CHAQUE requête côté serveur -->
<script nonce="{{ csp_nonce }}">
console.log("Ce script est autorisé car son nonce correspond au header CSP");
</script># Génération du nonce CSP par requête (FastAPI)
import secrets
@app.middleware("http")
async def csp_nonce_middleware(request: Request, call_next):
nonce = secrets.token_urlsafe(16)
request.state.csp_nonce = nonce
response = await call_next(request)
response.headers["Content-Security-Policy"] = (
f"default-src 'self'; script-src 'self' 'nonce-{nonce}'; object-src 'none'"
)
return response# Vérifier les headers de sécurité d'un site (audit rapide)
curl -sI https://technologik.local | grep -Ei "strict-transport|x-frame|x-content-type|content-security"
# Outils en ligne pour un audit complet (à lancer sur ses propres domaines)
# https://securityheaders.com
# https://observatory.mozilla.org# Mode "report-only" pour tester une CSP sans casser le site en production
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violation-report
# Le navigateur remonte les violations sans bloquer -> permet d'affiner la policy avant activation stricteRésumé
- HSTS, X-Frame-Options, X-Content-Type-Options sont des défenses simples à fort impact.
- La CSP est la défense la plus puissante contre le XSS résiduel : restreindre
script-srcau strict nécessaire. - Préférer un nonce par requête à
'unsafe-inline'pour les scripts inline légitimes. - Tester en
Content-Security-Policy-Report-Onlyavant de passer en mode bloquant strict.
Exercices pratiques
Mission : la CSP qui a bloqué le tableau de bord en production
Objectif : Diagnostiquer une CSP mal conçue à partir d'erreurs de console, puis la corriger sans réintroduire une faiblesse contre le XSS.
Contexte
Après l'activation d'une nouvelle Content-Security-Policy sur technologik.local, les utilisateurs signalent que le tableau de bord ne fonctionne plus : la console du navigateur affiche Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Le développeur qui a écrit ce script inline pour initialiser un graphique n'a pas anticipé la CSP.