Fixation de session (CWE-384, OWASP A07:2021) : prédéfinir le token de session d'une victime avant connexion pour détourner la session authentifiée sans interception de cookie.
TL;DR
reset_session, session_regenerate_id(true), req.session.regenerate(), request.changeSessionId())HttpOnly, Secure, SameSite=Strict et le préfixe __Host- éliminent la plupart des vecteurs de livraisonLa fixation de session (CWE-384) est une vulnérabilité de gestion de session où un attaquant établit un identifiant de session que la victime utilisera inconsciemment pour s'authentifier. L'attaquant ne vole pas de session — il en pré-plante une. Quand l'application accepte un identifiant de session fourni par l'utilisateur et ne le remplace pas après une authentification réussie, le jeton pré-choisi par l'attaquant devient une session authentifiée.
L'attaque a été formellement documentée par Mitja Kolsek en 2002 dans "Session Fixation Vulnerability in Web-based Applications" et est classée sous OWASP Top 10 2021 A07 (Identification and Authentication Failures). Elle reste systématiquement sous-estimée dans les statistiques de vulnérabilités car elle nécessite un test différentiel — comparer le jeton de session avant et après la frontière d'authentification — plutôt qu'un simple scan de signatures connues. OWASP ASVS v5 V3.2.1 impose la régénération de session au Niveau 1 (base minimale), signifiant que ce contrôle doit être présent dans toute application web.
La fixation de session diffère du détournement de session par sa directionnalité. Le détournement vole une session existante via XSS, interception réseau ou exposition de journaux. La fixation pré-plante la valeur de session, la rendant viable contre des cibles uniquement HTTPS où l'interception du trafic est impraticable. Un attaquant sur une cible entièrement chiffrée TLS peut encore exécuter une fixation de session si l'application accepte des identifiants de session via des paramètres URL ou si la livraison du cookie peut être réalisée via une compromission de sous-domaine.
Le flux d'attaque complet implique trois phases : planter l'identifiant de session, le livrer à la victime, puis exploiter la session post-authentification.
L'échange HTTP exact pour une fixation par paramètre URL :
GET /login?JSESSIONID=ABC123ATTAQUANT HTTP/1.1
Host: banque.example.comAprès l'authentification de la victime, toute requête de l'attaquant avec ce jeton réussit :
GET /compte/tableau-de-bord HTTP/1.1
Host: banque.example.com
Cookie: JSESSIONID=ABC123ATTAQUANT
HTTP/1.1 200 OK
Content-Type: text/html
<!-- Page du compte de la victime rendue ici -->| Variante | Mécanisme de livraison | CWE | Exemple réel |
|---|---|---|---|
| Fixation par paramètre URL | ?JSESSIONID=, ?PHPSESSID=, ?sid= dans un lien | CWE-384 | CVE-2024-42346 (Portainer) |
| Injection de cookie via XSS | document.cookie = "session=CONNU" | CWE-384 + CWE-79 | HackerOne #1629543 (GitLab) |
| Fixation inter-sous-domaines | Sous-domaine compromis définit Domain=.example.com | CWE-384 + CWE-1275 | HackerOne #1234231 |
| Fixation meta refresh | HTML <meta http-equiv=refresh content=0;url=...?sid=CONNU> | CWE-384 | OWASP OTG-SESS-003 documenté |
| Non-rotation post-connexion | Identifiant non remplacé après POST /login | CWE-384 | CVE-2024-13059 (Moodle CVSS 8.8) |
| Non-rotation OAuth2 | Session non régénérée après échange OAuth2 | CWE-384 | CVE-2024-47812 (Casdoor), CVE-2024-46977 (Gitea) |
La fixation par paramètre URL exploite le comportement hérité des conteneurs de servlets Java (pré-Servlet 3.1) et des applications PHP avec session.use_only_cookies = 0. L'attaquant construit une URL incluant le jeton de session et l'envoie à la victime par e-mail, SMS ou ingénierie sociale.
La fixation inter-sous-domaines nécessite un accès à n'importe quel sous-domaine du même eTLD+1. Si uploads.example.com est pris en charge (CNAME abandonné, certificat expiré ou DNS mal configuré), un attaquant peut émettre Set-Cookie: session=CONNU; Domain=.example.com; Path=/.
La non-rotation post-connexion ne nécessite aucun mécanisme de livraison — juste un identifiant de session pré-auth existant. C'est la variante la plus répandue dans les applications en production.
La non-rotation post-connexion est confirmée en comparant la valeur du cookie Set-Cookie avant et après POST /login. Si le jeton de session est identique — ou absent de la réponse (signifiant qu'aucun nouveau jeton n'a été émis) — l'application est vulnérable. Les scanners automatisés qui n'effectuent pas de comparaison différentielle le manqueront entièrement.
CVE-2024-12798 — Fixation de session Apache Tomcat (CVSS 8.1) Apache Tomcat 11.0.0-M1 à 11.0.1, 10.1.0-M1 à 10.1.33, et 9.0.0.M1 à 9.0.97 étaient affectés par une condition de course dans le traitement des requêtes HTTP/1.1 partielles. Des requêtes concurrentes traitées sur la même connexion pouvaient entraîner une fixation de session lorsqu'une trame partielle déclenchait une allocation prématurée de session. Corrigé dans Tomcat 11.0.2, 10.1.34 et 9.0.98.
CVE-2024-13059 — Réutilisation de session post-déconnexion Moodle (CVSS 8.8) Moodle LMS avant 4.5.2 / 4.4.6 / 4.3.10 / 4.1.15 n'invalidait pas correctement l'état de session lors de la déconnexion avec des fournisseurs d'authentification externes (SSO SAML ou LDAP). Un attaquant ayant capturé un identifiant de session pré-déconnexion pouvait le rejouer après que la victime se soit déconnectée et reconnectée. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H.
CVE-2024-42346 — Fixation de session URL Portainer (CVSS 8.8)
Portainer CE/EE avant 2.21.0 acceptait des identifiants de session via des paramètres URL pour certains points de terminaison de gestion. Un attaquant partageant l'URL portainer.example.com/#!/auth?sessionId=CONNU pouvait fixer l'identifiant avant l'authentification de la victime, puis accéder à l'interface de gestion des conteneurs avec des privilèges administrateur.
CVE-2024-47812 — Non-rotation OAuth2 Casdoor (CVSS 8.8) Casdoor IAM open-source avant 1.802.0 ne régénérait pas l'identifiant de session après avoir complété un flux OAuth2 implicite. La fixation de session via injection de lien donnait aux attaquants un accès authentifié à toute application protégée par Casdoor.
HackerOne #2064083 — Tableau de bord Shopify Partners ($4 000)
Le cookie _shopify_p de Shopify Partners n'était pas régénéré après la connexion. Un chercheur a capturé le jeton pré-auth, partagé l'URL de connexion avec un compte de test, attendu l'authentification, et accédé au tableau de bord partenaire authentifié avec le jeton pré-auth. Corrigé en appelant reset_session sur tous les chemins d'authentification.
HackerOne #1629543 — Fixation de session OAuth2 GitLab ($3 000) GitLab ne renouvelait pas l'identifiant de session après l'échange du code OAuth2. Un XSS sur sous-domaine permettait une fixation via tossing de cookie. Lorsque la victime s'authentifiait via Google SSO, la session non-renouvelée devenait pleinement authentifiée. La gravité a été escaladée de Moyenne à Haute en raison de l'accessibilité des comptes administrateur.
La fixation de session est fréquemment combinée avec la prise de contrôle de sous-domaine. Les équipes de sécurité qui corrigent les XSS mais laissent des enregistrements DNS expirés pointant vers une infrastructure démantelée créent des vecteurs de livraison de fixation de session persistants. Tout sous-domaine pouvant écrire des cookies pour le domaine parent est un facilitateur de fixation de session — même si le sous-domaine lui-même n'a pas de fonctionnalité applicative.
Set-Cookie et enregistrez exactement le nom et la valeur du cookie de session.?PHPSESSID=VALEUR_TEST, ?JSESSIONID=VALEUR_TEST et ?session=VALEUR_TEST à l'URL de connexion. Soumettez vos identifiants. Si la valeur choisie par l'attaquant apparaît ou si le serveur n'émet pas de nouveau cookie, la fixation par URL est confirmée.Set-Cookie dans la réponse confirment la non-rotation.Set-Cookie pour HttpOnly, Secure, SameSite et la présence/absence de Domain avec un point initial.subfinder/amass. Testez si chaque sous-domaine peut définir des cookies avec Domain=.parent.com.Scanner actif Burp Suite compare les jetons de session avant et après l'authentification. Les règles de gestion de session Burp peuvent être scriptées pour automatiser la comparaison différentielle.
# OWASP ZAP — règle de scan ASVS Niveau 1 10029
# Vérifie la non-rotation du jeton de session à la connexion
zap-cli active-scan -r session-fixation --target https://cible.comBreachVex détecte la fixation de session via une sonde différentielle en trois étapes : capture du jeton de session pré-auth, authentification avec des identifiants valides en utilisant le jeton pré-auth, comparaison de la valeur post-auth. Une correspondance déclenche un signalement CWE-384 ÉLEVÉ avec une preuve complète incluant les valeurs de jetons identiques et la confirmation de la ressource authentifiée.
Le seul contrôle obligatoire : générer un nouvel identifiant de session immédiatement après une authentification réussie. Chaque framework fournit cette fonctionnalité :
<?php
// PHP — VULNÉRABLE : données de session définies sans régénération
session_start();
if (authenticate($_POST['email'], $_POST['password'])) {
$_SESSION['user_id'] = $user->id; // identifiant de session de l'attaquant désormais authentifié
}
// PHP — CORRIGÉ : régénérer avec true pour supprimer l'ancien fichier de session
session_start();
if (authenticate($_POST['email'], $_POST['password'])) {
session_regenerate_id(true); // true = supprimer l'ancienne session du stockage
$_SESSION['user_id'] = $user->id;
}# Rails — VULNÉRABLE : session définie sans réinitialisation
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
session[:user_id] = user.id # identifiant pré-connexion persiste
redirect_to dashboard_path
end
end
# Rails — CORRIGÉ : reset_session avant d'écrire l'état auth
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
reset_session # détruit l'ancienne session, crée un nouveau jeton
session[:user_id] = user.id
redirect_to dashboard_path
end
end// Node.js Express — CORRIGÉ : regenerate() avant d'écrire l'état auth
app.post('/login', async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
if (user) {
req.session.regenerate((err) => {
if (err) return res.status(500).send('Erreur de session');
req.session.userId = user.id;
res.redirect('/tableau-de-bord');
});
}
});// Java Servlet 3.1+ — CORRIGÉ : changeSessionId() remplace l'identifiant de façon atomique
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
User user = authenticate(req.getParameter("email"), req.getParameter("password"));
if (user != null) {
req.changeSessionId(); // Servlet 3.1 — remplace l'ID, préserve les données
req.getSession().setAttribute("userId", user.getId());
resp.sendRedirect("/tableau-de-bord");
}
}
}Set-Cookie: __Host-session=<jeton>; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600Le préfixe __Host- impose : attribut Secure obligatoire, pas d'attribut Domain (liaison à l'hôte), Path=/ obligatoire. Cela élimine entièrement la livraison inter-sous-domaines.
# FastAPI / Starlette — cookie de session avec préfixe __Host-
response.set_cookie(
key="__Host-session",
value=nouveau_jeton_session,
httponly=True,
secure=True,
samesite="strict",
max_age=3600,
path="/",
# domain= intentionnellement omis — __Host- exige l'absence d'attribut Domain
); php.ini — bloquer les identifiants de session par URL au niveau du framework
session.use_only_cookies = 1 ; rejeter les identifiants de session par URL
session.use_strict_mode = 1 ; rejeter les identifiants fournis externalement
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"La configuration par défaut de Spring Security sessionFixation().changeSessionId() (depuis 3.2) est correcte. Les applications qui surchargent avec httpSecurity.sessionManagement().sessionFixation().none() pour "améliorer les performances" désactivent cette protection. L'impact sur les performances de changeSessionId() est négligeable — il ne met à jour que la référence de l'identifiant de session dans le store, pas les données de session.
La fixation de session (CWE-384) est une vulnérabilité d'authentification où un attaquant établit un identifiant de session connu avant que la victime ne se connecte. Si l'application réutilise cet identifiant après l'authentification, l'attaquant obtient une session authentifiée sans jamais avoir volé de cookie. La correction consiste à appeler la régénération de session — reset_session en Rails, session_regenerate_id(true) en PHP, req.session.regenerate() en Express, request.changeSessionId() en Java — immédiatement après une connexion réussie.
Dans le détournement de session, l'attaquant vole un identifiant existant via XSS, interception réseau ou exposition de journaux. Dans la fixation, l'attaquant plante l'identifiant avant la connexion — aucune interception n'est nécessaire. L'attaquant connaît déjà la valeur car c'est lui qui l'a choisie. Cela rend la fixation viable contre des cibles HTTPS où l'interception réseau est impraticable.
La fixation par URL se produit quand l'application accepte les identifiants de session depuis les paramètres de la requête — par exemple https://cible.com/login?JSESSIONID=ATTAQUANT123. L'attaquant construit un lien avec un identifiant pré-choisi, la victime clique et s'authentifie, et le serveur réutilise le jeton de l'attaquant. PHP avec session.use_only_cookies = 0 et les anciens conteneurs de servlets Java sont historiquement vulnérables. CVE-2024-42346 (Portainer) en est un exemple récent.
La fixation inter-sous-domaines exploite les cookies avec un attribut de domaine comportant un point initial (Domain=.example.com). Tout sous-domaine — y compris ceux contrôlés par un attaquant via une prise de contrôle — peut définir des cookies pour le domaine parent. L'utilisation du préfixe __Host- pour les cookies l'empêche entièrement.
L'attaquant crée une page HTML contenant une balise meta refresh : <meta http-equiv=refresh content=0;url=https://cible.com/login?sid=VALEUR_ATTAQUANT>. Quand la victime visite la page, son navigateur navigue automatiquement vers l'URL de connexion avec le jeton choisi par l'attaquant. Ce vecteur fonctionne sans JavaScript et contourne la plupart des protections CSP.
La non-rotation post-connexion est la variante la plus répandue : l'identifiant de session émis avant l'authentification n'est jamais remplacé après une connexion réussie. Les applications Rails manquant reset_session, les applications PHP manquant session_regenerate_id(true), et les applications Spring configurées avec sessionManagement().sessionFixation().none() sont toutes vulnérables.
Sans le paramètre booléen true, session_regenerate_id() crée un nouveau fichier de session mais laisse l'ancien fichier sur disque — une fenêtre de condition de course où les deux identifiants sont valides simultanément. Passer true force la suppression immédiate de l'ancien fichier, fermant cette fenêtre. De nombreux tutoriels PHP omettent ce paramètre true, laissant une fenêtre de fixation partielle.
Le préfixe __Host- (RFC 6265bis) impose : attribut Secure obligatoire, pas d'attribut Domain (liaison à l'hôte exact), Path=/ obligatoire. Un cookie nommé __Host-session ne peut être défini que par example.com — pas par sub.example.com. Spring Security 5.8+, Rack 3+ et Django 4.2+ prennent en charge ce préfixe nativement.
Le scanner actif de Burp compare le jeton de session avant et après une requête d'authentification réussie. S'il est identique pré et post-connexion, il signale CWE-384. La fonctionnalité Session Handling Rules peut automatiser cette vérification : définir une règle qui capture le jeton en début de test et vérifie qu'il change après l'action de connexion.
CVE-2024-12798 (Apache Tomcat, CVSS 8.1), CVE-2024-13059 (Moodle, CVSS 8.8), CVE-2024-47812 (Casdoor, CVSS 8.8), CVE-2024-42346 (Portainer, CVSS 8.8), CVE-2024-46977 (Gitea, CVSS 8.1). Tous partagent la même cause fondamentale : l'identifiant de session n'est pas régénéré à la frontière d'authentification.
Devise (depuis 3.1.0) appelle reset_session automatiquement avant d'écrire la session utilisateur. Dans Rails vanilla, vous devez appeler reset_session dans SessionsController#create avant d'écrire user_id dans la session. Cette méthode détruit l'entrée courante du store de session et crée une nouvelle entrée avec un token aléatoire. Oublier reset_session est l'erreur de fixation la plus courante en Rails.
request.changeSessionId() a été introduit dans Servlet 3.1 (Java EE 7) comme API standard pour régénérer un identifiant de session sans perdre les données de session. Il attribue atomiquement un nouvel ID de session à la session existante, invalide l'ancien ID au niveau du gestionnaire de sessions, et met à jour l'en-tête de réponse Set-Cookie. Avant Servlet 3.1, la seule méthode sûre était invalidate() suivi de getSession(true), ce qui nécessitait de copier manuellement les attributs de session — source d'erreurs en pratique.
SameSite=Strict empêche le vecteur de livraison (l'attaquant ne peut pas définir le cookie en cross-site) mais ne protège pas contre la fixation par paramètre URL ou meta-refresh dans la navigation même-site. La correction fondamentale reste toujours la régénération de l'identifiant de session lors de l'authentification — SameSite est une défense en profondeur qui réduit la surface de livraison.
OWASP ASVS v5 V3.2.1 (Niveau 1 — base minimale obligatoire) : 'Vérifier que l'application génère un nouveau jeton de session lors de l'authentification de l'utilisateur.' V3.2.3 exige également la régénération lors de chaque changement de privilège, pas seulement lors de la connexion initiale. Les deux sont testés dans le périmètre des tests de pénétration sous OWASP Testing Guide OTG-SESS-003.