Comment empêcher une attaque de fixation de session
Section intitulée « Comment empêcher une attaque de fixation de session »La fixation de session consiste à faire utiliser à une victime un identifiant de session déjà connu par l’attaquant, puis à attendre son authentification. Le défaut n’est pas l’absence de mot de passe, ni forcément une faiblesse cryptographique : c’est la conservation d’un identifiant non fiable lors d’un changement de privilège.
Cela ressemble à un visiteur qui choisit à l’avance le numéro du ticket remis à quelqu’un devant entrer dans un lieu contrôlé. Si le ticket garde le même numéro après le contrôle, la personne qui connaît ce numéro peut profiter de l’accès accordé. Le problème se cache dans le passage entre deux états de confiance.
→ Lire l’explication complète de la marmotte
Mécanisme de la fixation de session
Section intitulée « Mécanisme de la fixation de session »Une session associe un identifiant opaque, généralement placé dans un cookie HTTP (Hypertext Transfer Protocol), à un état serveur : visite anonyme, compte authentifié, droits applicatifs, jetons d’accès secondaires ou panier. L’identifiant doit être imprévisible, mais cette propriété ne suffit pas. Un identifiant imprévisible peut être imposé à une victime par un canal que l’application accepte.
L’attaque suit quatre conditions :
- l’attaquant obtient ou choisit un identifiant de session valide ;
- l’application accepte cet identifiant depuis une requête, une URL, un sous-domaine contrôlé ou un cookie injecté ;
- la victime s’authentifie sans que l’identifiant soit remplacé ;
- l’attaquant réutilise le même identifiant pour accéder à la session désormais authentifiée.
Le point critique est le changement de niveau de privilège. Une session anonyme et une session authentifiée ne portent pas la même confiance. Conserver le même identifiant entre les deux revient à accepter qu’un artefact contrôlé avant connexion devienne une preuve d’authentification après connexion. C’est précisément le genre de raccourci qui passe en démonstration produit, puis nourrit un rapport d’incident beaucoup moins glamour.
Les vecteurs classiques sont les paramètres d’URL (?PHPSESSID=...), les liens envoyés par courriel, les redirections, un cookie posé depuis un sous-domaine compromis, un proxy qui réécrit des en-têtes, ou une application qui accepte explicitement un identifiant fourni par le client. Les anciennes configurations qui réécrivent automatiquement les URL avec l’identifiant de session méritent une extinction sans cérémonie.
Un cookie avec les attributs Secure, HttpOnly et SameSite réduit des voies d’injection ou de vol, mais ne corrige pas une fixation acceptée par l’application. HttpOnly n’empêche pas un navigateur de joindre un cookie déjà posé. SameSite ne régénère rien. Les attributs de cookie sont des garde-corps, pas une machine à remonter le temps.
La surface d’attaque recoupe les erreurs décrites dans les failles applicatives : confiance excessive dans une donnée cliente, confusion entre anonymat et authentification, et propagation de session entre domaines. Une architecture d’identités bien pensée ne dispense pas de traiter cette frontière dans chaque application consommatrice.
Conditions de laboratoire et démonstration reproductible
Section intitulée « Conditions de laboratoire et démonstration reproductible »La démonstration suivante est volontairement vulnérable. Elle doit rester sur localhost, dans un conteneur ou une machine de test isolée. Aucun compte réel, aucun cookie de navigateur habituel, aucun service exposé. Tester une fixation sur un système sans autorisation reste une intrusion, même avec un vocabulaire plus élégant autour.
Prérequis :
- PHP 8.2 ou supérieur avec le serveur de développement intégré ;
curl;- un répertoire de travail local ;
- aucun reverse proxy devant le laboratoire, afin de voir les en-têtes de session sans transformation.
Créer index.php avec le contenu suivant :
<?php// Laboratoire vulnérable : accepte un identifiant de session fourni dans l'URL.ini_set('session.use_only_cookies', '0');ini_set('session.use_strict_mode', '0');session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['login'] ?? '') === 'alice' && ($_POST['password'] ?? '') === 'demo') { // Vulnérabilité intentionnelle : l'identifiant courant est conservé. $_SESSION['user'] = 'alice'; $_SESSION['role'] = 'admin'; header('Location: /index.php'); exit;}
if (isset($_SESSION['user'])) { echo "Connecté : " . htmlspecialchars($_SESSION['user']) . "\n"; echo "Rôle : " . htmlspecialchars($_SESSION['role']) . "\n"; exit;}?><form method="post"> <input name="login" value="alice"> <input name="password" type="password" value="demo"> <button>Connexion</button></form>Démarrer le serveur, puis rejouer le scénario avec un identifiant contrôlé :
# Terminal 1 : démarrer le laboratoire local.php -S 127.0.0.1:8080 index.php
# Terminal 2 : l'attaquant impose un identifiant connu via l'URL.# Le fichier victime.cookies représente le navigateur de la victime.curl -s -c victime.cookies \ 'http://127.0.0.1:8080/index.php?PHPSESSID=fixation-lab-123'
# La victime s'authentifie, mais l'application ne change pas de session.curl -s -b victime.cookies -c victime.cookies \ -d 'login=alice&password=demo' \ http://127.0.0.1:8080/index.php
# L'attaquant rejoue l'identifiant fixé et récupère la session authentifiée.curl -s -H 'Cookie: PHPSESSID=fixation-lab-123' \ http://127.0.0.1:8080/index.phpLe dernier appel retourne Connecté : alice et Rôle : admin. Le serveur a correctement vérifié le mot de passe, mais a attaché ce résultat à une session connue d’un tiers. Le contrôle d’authentification n’est donc pas cassé ; son résultat est livré au mauvais détenteur de session.
Dans un environnement réel, l’identifiant est souvent aléatoire et l’attaquant ne peut pas en inventer un qui corresponde à un état serveur existant. Cela ne protège pas lorsque le serveur crée la session demandée, accepte un identifiant fourni sans vérification stricte, ou lorsque l’attaquant obtient d’abord une session anonyme légitime. L’entropie n’annule pas la confusion de confiance. Elle masque seulement le problème jusqu’au prochain audit.
Régénération d’identifiant après authentification
Section intitulée « Régénération d’identifiant après authentification »La contre-mesure obligatoire est la régénération de l’identifiant de session à chaque élévation de privilège. Après validation des identifiants, le serveur doit créer un nouvel identifiant cryptographiquement aléatoire, associer l’état authentifié à ce nouvel identifiant, puis invalider l’ancien.
En PHP, la correction du laboratoire tient à une instruction placée après la validation des identifiants et avant toute donnée de réponse :
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['login'] ?? '') === 'alice' && ($_POST['password'] ?? '') === 'demo') { // Remplace l'identifiant imposé avant authentification. // true invalide immédiatement l'ancien stockage de session. session_regenerate_id(true);
$_SESSION['user'] = 'alice'; $_SESSION['role'] = 'admin'; header('Location: /index.php'); exit;}La régénération doit aussi intervenir après une authentification multifacteur, une réauthentification sensible, une élévation vers un rôle d’administration, un changement de locataire, la validation d’un lien de récupération de compte et toute restauration de session depuis un jeton de longue durée. Un changement de privilège sans rotation d’identifiant est une dette de sécurité avec intérêts composés.
Le compromis central est net : régénérer invalide les requêtes concurrentes qui portent l’ancien identifiant et peut casser des onglets ouverts ou des appels asynchrones en vol. Conserver l’ancien identifiant évite ces frictions, mais autorise le détournement de l’état authentifié. Le choix doit être tranché : invalider immédiatement lors d’une authentification ou d’une élévation. Pour les flux nécessitant une transition tolérante, conserver l’ancien identifiant pendant quelques secondes, marqué non authentifié et inutilisable pour toute action métier. Ne jamais conserver deux sessions authentifiées équivalentes pour ménager un onglet. Un onglet peut se reconnecter, un compte compromis ne se déconnecte pas tout seul par politesse.
Les frameworks modernes fournissent souvent une primitive dédiée : session.regenerate(), changeSessionId(), rotation de jeton ou renouvellement de contexte de sécurité. Son invocation doit être vérifiée dans le code applicatif, pas supposée depuis une documentation. Une fédération d’identité ne résout pas automatiquement ce point : après un retour OpenID Connect (OIDC), l’application locale doit également renouveler sa propre session. Le fournisseur d’identité authentifie le sujet ; il ne nettoie pas les erreurs de session du client applicatif.
Cookies, domaines et contre-mesures complémentaires
Section intitulée « Cookies, domaines et contre-mesures complémentaires »Restreindre le cookie de session réduit les moyens de fixation :
- définir
Securepour interdire le transport en HTTP non chiffré ; - définir
HttpOnlypour limiter l’exposition aux scripts exécutés dans la page ; - définir
SameSite=Laxau minimum, ouSameSite=Strictlorsque les parcours intersites le permettent ; - omettre l’attribut
Domainafin de créer un cookie hôte seul ; - fixer
Pathau périmètre minimal ; - désactiver les identifiants de session dans les URL ;
- activer le mode strict du gestionnaire de session afin de refuser les identifiants inconnus fournis par le client.
L’attribut Domain=.example.test permet à tout sous-domaine de poser un cookie pour le domaine parent. Si blog.example.test est repris, mal isolé ou sert du contenu utilisateur, app.example.test hérite potentiellement d’un voisin particulièrement bruyant. Séparer les applications sensibles sur des domaines distincts reste préférable à la croyance selon laquelle tous les sous-domaines resteront éternellement disciplinés.
Le préfixe __Host- apporte une contrainte utile pour les cookies modernes : il impose Secure, interdit Domain et exige Path=/. Un cookie de session nommé __Host-session réduit la capacité d’un sous-domaine à l’écraser. Ce mécanisme dépend du navigateur, mais son coût est faible et son intention est sans ambiguïté.
Ces mesures ne garantissent pas l’absence de vol de session par script intersite, interception de poste, malware navigateur ou fuite dans les journaux. Elles ne remplacent ni la protection contre les attaques par scripts injectés, ni la validation des redirections, ni une politique de durée de session. Une session régénérée reste volable après sa création. La fixation concerne l’avant-authentification ; le détournement de session couvre aussi l’après-authentification.
Exploitation, journalisation et reprise de session
Section intitulée « Exploitation, journalisation et reprise de session »Une session authentifiée doit avoir une durée d’inactivité courte, une durée absolue définie et une révocation serveur immédiate lors de déconnexion, changement de mot de passe, désactivation de compte ou signalement de compromission. Un jeton signé autonome complique cette révocation : sans liste de révocation ou durée très courte, le serveur ne peut pas retirer un droit déjà distribué. Les jetons qui ne meurent jamais sont une tradition regrettable, au même rayon que les mots de passe dans les variables d’environnement.
Journaliser sans stocker la valeur brute du cookie. Enregistrer un hachage dédié de l’identifiant, l’heure de création, les rotations, le compte associé, le niveau d’authentification, l’adresse réseau avec sa politique de minimisation, et le motif de révocation. Un hachage avec une clé de journalisation distincte évite qu’un export de logs devienne un distributeur de sessions.
La supervision doit détecter une réutilisation d’un ancien identifiant après rotation, des rotations anormalement fréquentes, un changement brutal de contexte réseau ou d’agent utilisateur, et les authentifications réussies suivies de requêtes concurrentes depuis des contextes incompatibles. Ces signaux déclenchent une invalidation ou une réauthentification, pas une certitude automatique : les réseaux mobiles savent changer d’adresse sans demander l’avis de personne.
Le stockage de session exige une sauvegarde cohérente et chiffrée lorsqu’il contient des données d’autorisation. Restaurer une sauvegarde peut réintroduire des sessions pourtant révoquées. Après reprise, invalider globalement les sessions restaurées ou conserver un compteur de génération hors de la sauvegarde, vérifié à chaque requête. Une reprise qui ressuscite les accès supprimés n’est pas une reprise, c’est une séance de spiritisme opérationnel.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans régénération, tout ce que l’attaquant a placé dans la session anonyme survit à la connexion : un jeton anti falsification de requête (CSRF) créé avec la session lui reste connu et permet de forger des requêtes acceptées au nom de la victime. Une adresse de retour, un panier ou une préférence semés à l’avance sont ensuite exploités par un compte authentifié. La traçabilité se dégrade aussi : les journaux montrent un seul identifiant continu depuis la visite anonyme, et les actions de l’attaquant y sont attribuées au compte de la victime. Faute de rotation, aucun « ancien identifiant » n’existe, donc la détection de réutilisation après rotation ne peut jamais se déclencher. Enfin, chaque requête de l’une ou l’autre partie prolonge l’inactivité et maintient la session partagée ouverte.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Une application garde souvent un numéro dans le navigateur après une première visite. Ce numéro permet au serveur de retrouver des informations temporaires : la visite en cours, puis éventuellement le compte connecté. L’attaque commence lorsqu’une personne malveillante réussit à faire utiliser à sa cible un numéro qu’elle connaît déjà. Cela peut arriver avec une adresse contenant ce numéro, un réglage ancien, ou un cookie placé depuis un autre site lié au même domaine.
La victime ouvre ensuite le site et se connecte avec son mot de passe. Si le site conserve le même numéro après cette connexion, le serveur associe ce numéro au compte de la victime. La personne malveillante envoie alors ce numéro au serveur depuis son propre navigateur. Le serveur le reconnaît et lui donne accès à la session connectée.
La correction consiste à remplacer le numéro juste après chaque étape qui donne davantage de droits : connexion, validation d’un second facteur, accès administrateur ou récupération de compte. L’ancien numéro doit devenir inutilisable. Le site doit aussi refuser les numéros inconnus imposés par les visiteurs, ne pas mettre ces numéros dans les adresses, et limiter qui peut créer les cookies concernés. Si le remplacement échoue, les anciennes pages ouvertes peuvent demander une nouvelle connexion : c’est gênant, mais cela évite de transformer un numéro connu en accès à un compte.
À lire aussi
- Upload de shell PHP déguisé en image dans un CMSDémonstration en laboratoire de l'exécution de code par téléversement d'un fichier PHP maquillé en image, avec le mécanisme de la confusion de type et les contre-mesures de validation de contenu et de désactivation d'exécution.
- Comment corriger une attaque path traversalDémonstration d'une attaque path traversal sur un téléchargement vulnérable et des contrôles nécessaires pour empêcher la lecture ou l'écriture hors périmètre.
- Comment prévenir une attaque Host header injectionCette fiche démontre l'attaque par manipulation d'en-tête Host, ses impacts sur les liens générés et la validation d'origine, puis les contre-mesures.
- Comment bloquer un webhook forgé sans signature vérifiéeDémonstration d’une attaque de webhook non vérifié, de son mécanisme en laboratoire et des contre-mesures de signature, anti-rejeu et exploitation.
Pages voisines
La citent
- Faille Facebook Voir en tant que 2018 et vol de jetons d'accès
- Déployer WebAuthn FIDO2 passwordless sur une application web
Comment empêcher une attaque de fixation de session
Elle cite
: les deux pages se citent l’une l’autre.
