cyber / cybersecurite-fondamentale
Sécurité web : cookies sécurisés et CORS
Explication
Ce que vous allez apprendre
- Configurer un cookie de session avec HttpOnly, Secure et SameSite comme réglages par défaut
- Comprendre le rôle du préfixe
__Host-pour durcir encore un cookie sensible - Expliquer pourquoi la Same-Origin Policy bloque par défaut les requêtes cross-domaine
- Configurer CORS avec une liste blanche stricte plutôt qu'une réflexion aveugle de l'en-tête Origin
- Identifier la combinaison dangereuse
allow_credentials=True+ wildcard d'origine
Dans quel contexte ?
Un site tiers malveillant embarque un script qui appelle directement l'API interne d'une entreprise depuis le navigateur d'une victime déjà connectée. Si l'API répond avec Access-Control-Allow-Origin réglé sur l'origine du site malveillant et autorise les cookies, le navigateur laisse passer la requête authentifiée sans broncher. Une configuration CORS avec liste blanche stricte aurait bloqué cet appel avant même qu'il n'atteigne le serveur.
Le cookie, petit fichier au grand pouvoir
Un cookie de session est ce qui permet à un site de se souvenir que vous êtes connecté d'une requête à l'autre. Mal protégé, c'est aussi l'un des vecteurs de vol de session les plus recherchés par un attaquant : le récupérer équivaut souvent à usurper complètement l'identité de la victime, sans même connaître son mot de passe.
Trois attributs qui changent tout
| Attribut | Ce qu'il protège | Contre quelle attaque |
|---|---|---|
| HttpOnly | Interdit la lecture du cookie en JavaScript | Vol de session via XSS (leçon 9) |
| Secure | Interdit la transmission du cookie en clair | Interception réseau |
| SameSite | Contrôle l'envoi du cookie vers des sites tiers | CSRF (leçon dédiée) |
Ensemble, ces trois attributs devraient être la configuration par défaut de TOUT cookie de session, sans exception.
CORS : une porte que le navigateur garde fermée par défaut
Par défaut, le navigateur empêche une page chargée depuis un domaine d'appeler l'API d'un autre domaine — c'est la "same-origin policy". CORS est le mécanisme qui permet à un serveur d'ouvrir explicitement cette porte à certains domaines de confiance, via des en-têtes de réponse HTTP.
Le piège le plus dangereux de cette leçon
Piège fréquent
Un serveur qui répond simplement "oui" à n'importe quelle origine demandée (en reflétant aveuglément l'en-tête Origin reçu) tout en autorisant l'envoi des cookies (allow_credentials=True) annule complètement la protection CORS : n'importe quel site tiers peut alors faire des requêtes authentifiées vers votre API en votre nom. La règle est simple : une liste blanche explicite d'origines de confiance, jamais une réflexion automatique de ce que le client prétend être.
Bonne pratique
Préfixez vos cookies sensibles avec __Host- : le navigateur impose alors automatiquement Secure, l'absence d'attribut Domain et Path=/, ce qui rend le cookie beaucoup plus difficile à falsifier depuis un sous-domaine compromis.
Un détail qui traîne souvent
Les listes blanches CORS accumulent parfois des URL de test ou de staging oubliées après un déploiement — une revue périodique de cette configuration évite qu'un domaine de test compromis devienne une porte d'entrée vers la production.
Commandes & code
Sécurité web : cookies sécurisés et CORS
# Attributs de cookie de sécurité
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=28800HttpOnly : inaccessible en JavaScript (document.cookie) -> mitige le vol de session via XSS
Secure : cookie envoyé UNIQUEMENT sur HTTPS -> mitige l'interception réseau
SameSite : contrôle l'envoi cross-site -> mitige le CSRF (voir leçon dédiée)
Path : restreint le cookie à un chemin précis de l'application
Max-Age / Expires : durée de vie explicite, évite les cookies "éternels"
__Host- (préfixe) : impose Secure + Path=/ + pas de Domain -> le cookie le plus difficile à falsifier# Configuration de cookie sécurisé dans FastAPI
from fastapi import Response
@app.post("/login")
async def login(response: Response, credentials: LoginSchema):
session_token = create_session(...)
response.set_cookie(
key="__Host-session", # préfixe __Host- : navigateur impose Secure, pas de Domain, Path=/
value=session_token,
httponly=True,
secure=True,
samesite="strict",
max_age=28800, # 8 heures
path="/",
)
return {"status": "connecté"}# CORS (Cross-Origin Resource Sharing) : autorise EXPLICITEMENT un domaine tiers à appeler ton API
# Par défaut, le navigateur bloque les requêtes cross-origin (Same-Origin Policy) - CORS est une exception contrôlée# VULNÉRABLE : CORS trop permissif, reflète n'importe quelle origine avec credentials
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # accepte TOUT domaine
allow_credentials=True, # ET autorise l'envoi de cookies -> combinaison dangereuse
allow_methods=["*"],
allow_headers=["*"],
)
# NOTE : les navigateurs interdisent en réalité "*" + credentials=True simultanément,
# mais des implémentations custom qui reflètent Origin dynamiquement recréent ce trou.# VULNÉRABLE (variante fréquente) : réflexion naïve de l'en-tête Origin sans liste blanche
@app.middleware("http")
async def bad_cors(request: Request, call_next):
response = await call_next(request)
origin = request.headers.get("origin", "*")
response.headers["Access-Control-Allow-Origin"] = origin # accepte AVEUGLÉMENT n'importe quel Origin
response.headers["Access-Control-Allow-Credentials"] = "true"
return response# CORRECT : liste blanche stricte d'origines de confiance
from fastapi.middleware.cors import CORSMiddleware
ALLOWED_ORIGINS = [
"https://technologik.local",
"https://app.technologik.local",
]
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS, # liste explicite, jamais de wildcard avec credentials
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization", "X-CSRF-Token"],
max_age=600, # durée de cache du préflight OPTIONS
)# Anatomie d'une requête préflight CORS (envoyée automatiquement par le navigateur avant une requête "non simple")
OPTIONS /api/courses HTTP/1.1
Origin: https://app.technologik.local
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: authorization
# Réponse attendue du serveur pour autoriser la requête réelle qui suivra
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.technologik.local
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Credentials: true# Checklist cookies & CORS
- HttpOnly + Secure + SameSite sur tout cookie de session, sans exception
- Jamais de wildcard "*" en Access-Control-Allow-Origin quand allow_credentials=True
- Liste blanche explicite d'origines, jamais de réflexion aveugle de l'en-tête Origin
- Vérifier régulièrement que la liste blanche CORS ne contient pas d'environnements de test oubliésRésumé
- Les attributs
HttpOnly,Secure,SameSitesont la base de tout cookie de session. - Le préfixe
__Host-durcit encore la protection contre la falsification de cookie. - CORS doit toujours reposer sur une liste blanche explicite, jamais sur une réflexion aveugle de
Origin. allow_credentials=Truecombiné à un wildcard d'origine est une faille à elle seule.
Exercices pratiques
Mission : le middleware CORS qui accepte tout le monde
Objectif : Repérer une configuration CORS dangereuse dans du code réel et la remplacer par une liste blanche stricte.
Contexte
Un audit de sécurité de l'API Technologik trouve ce middleware en production :
@app.middleware("http")
async def bad_cors(request, call_next):
response = await call_next(request)
origin = request.headers.get("origin", "*")
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
return responseUn site totalement étranger à Technologik, recettes-de-cuisine.example, parvient à faire des appels authentifiés vers l'API depuis le navigateur d'un utilisateur déjà connecté.