Session fixation via paramètre URL (CWE-384) : l'attaquant partage un lien contenant un token prédéfini que la victime authentifie, accordant une prise de contrôle complète du compte.
TL;DR
?PHPSESSID= et ?JSESSIONID= — l'attaquant partage un lien de connexion avec un identifiant pré-choisisession.use_only_cookies = 0) — changer à 1 immédiatement<tracking-mode>COOKIE</tracking-mode>La fixation par paramètre URL est la variante la plus ancienne et la plus directe de la classe d'attaque de fixation de session. L'application lit un identifiant de session depuis la chaîne de requête URL ou le paramètre de chemin plutôt que exclusivement depuis les cookies, permettant à un attaquant de construire une URL contenant un jeton de session pré-choisi et de la livrer à la victime comme un lien applicatif d'apparence légitime.
Le mécanisme d'attaque ne nécessite ni XSS, ni interception réseau, ni compromission de sous-domaine. L'attaquant a seulement besoin de :
?PHPSESSID=TEST ou ?JSESSIONID=TEST)Les identifiants de session embarqués dans les URL créent un risque secondaire au-delà de la fixation : la fuite via les en-têtes Referer. Quand un utilisateur visite toute ressource externe depuis une page dont l'URL contient un jeton de session — un script d'analyse, un bouton de partage social — le navigateur envoie l'URL complète (incluant l'identifiant de session) dans l'en-tête Referer. Les identifiants de session dans les URL sont donc exfiltrés vers chaque tiers de la page.
Comportement PHP avec session.use_only_cookies = 0 (ancienne valeur par défaut) :
GET /login?PHPSESSID=ID_CONNU_ATTAQUANT_123 HTTP/1.1
Host: cible.com
HTTP/1.1 200 OK
Set-Cookie: PHPSESSID=ID_CONNU_ATTAQUANT_123; path=/
-- Le serveur a accepté l'identifiant fourni par URL --
POST /login HTTP/1.1
Cookie: PHPSESSID=ID_CONNU_ATTAQUANT_123
email=victime@cible.com&password=correct
HTTP/1.1 302 Found
Location: /tableau-de-bord
-- Pas de Set-Cookie : PHPSESSID non renouvelé --
-- L'attaquant envoie : --
GET /tableau-de-bord HTTP/1.1
Cookie: PHPSESSID=ID_CONNU_ATTAQUANT_123
HTTP/1.1 200 OK -- tableau de bord de la victime --Format de réécriture URL des servlets Java :
GET /tableau-de-bord;jsessionid=ID_CONNU_ATTAQUANT_123 HTTP/1.1
Host: cible.com
-- Format de paramètre chemin avec point-virgule — plus difficile à filtrer --| Format | Framework | Exemple URL | Risque |
|---|---|---|---|
?PHPSESSID= | PHP | /login?PHPSESSID=CONNU | CWE-384 |
?JSESSIONID= | Java | /login?JSESSIONID=CONNU | CWE-384 |
;jsessionid= | Java (chemin) | /page;jsessionid=CONNU | CWE-384 + contournement WAF |
?session= | Générique | /login?session=CONNU | CWE-384 |
?sid= | Générique | /login?sid=CONNU | CWE-384 |
#access_token= | SPA/OAuth2 | /callback#access_token=JWT | CWE-598 / fuite de jeton |
La fuite Referer transforme les sessions embarquées dans URL en vecteur d'exfiltration passive. Chaque chargement de page incluant l'identifiant de session dans l'URL envoie cet identifiant aux :
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 dans les URL de l'interface de gestion. Un attaquant pouvait construire portainer.internal.example.com/#!/auth?sessionId=ATTAQUANT_CHOISI et la partager avec un administrateur. Quand l'administrateur s'authentifiait via le lien construit, le jeton choisi par l'attaquant devenait une session admin authentifiée avec un accès complet à la gestion Docker/Kubernetes. Corrigé dans Portainer 2.21.0.
HackerOne #963569 — Fuite Referer JSESSIONID IBM ($2 500)
IBM WebSphere Application Server exposait JSESSIONID dans des URL pour un sous-ensemble de points de terminaison hérités. Quand des utilisateurs authentifiés naviguaient vers du contenu externe, l'en-tête Referer contenait l'URL complète incluant le JSESSIONID. Des tiers recevant le Referer pouvaient extraire le jeton de session. La vulnérabilité combinait CWE-598 et CWE-384.
CVE-2024-46977 — Non-rotation de session OAuth2 Gitea (CVSS 8.1) Gitea avant 1.22.4 embarquait des paramètres de session dans les URL de redirection OAuth2. Combiné avec la vulnérabilité de non-rotation, les identifiants de session apparaissaient dans l'historique du navigateur et les journaux d'accès serveur avec la même valeur avant et après l'authentification.
Fuite du paramètre CALID Apple Calendar (2019) Apple Calendar Server utilisait des identifiants de calendrier (CALID) comme paramètres URL pour les liens de partage de calendrier. Ces identifiants fonctionnaient comme des identifiants de session persistants pour l'accès aux données de calendrier. Quand des utilisateurs avec des liens de calendrier dans leurs marque-pages naviguaient vers du contenu externe, le CALID fuitait via les en-têtes Referer vers des fournisseurs d'analyse et CDN.
Tester la livraison de session par URL prend moins de 30 secondes : ajouter ?PHPSESSID=TEST_FIXATION à l'URL de connexion et vérifier si l'application définit cette valeur comme cookie. De nombreuses applications PHP déployées avant 2015 — et non mises à jour depuis — ont session.use_only_cookies = 0 comme valeur par défaut. Cette vérification de configuration devrait figurer dans chaque checklist de test de pénétration d'application web.
?PHPSESSID=TEST_FIXATION_URL à l'URL de connexion de l'application. Soumettre la requête. Vérifier si TEST_FIXATION_URL apparaît dans l'en-tête Set-Cookie de la réponse. Si oui, la livraison de session par URL est confirmée.?JSESSIONID=TEST_FIXATION_URL et ?session=TEST_FIXATION_URL et ;jsessionid=TEST_FIXATION_URL (paramètre chemin).# Test rapide de livraison de session par URL PHP
SESSION_TEST="TEST_FIXATION_URL_$(date +%s)"
REPONSE=$(curl -sv "https://cible.com/login?PHPSESSID=${SESSION_TEST}" 2>&1)
echo "$REPONSE" | grep -i "set-cookie" | grep -i "$SESSION_TEST" && echo "VULNÉRABLE : livraison de session par URL confirmée"
# Test Java
curl -sv "https://cible.com/login?JSESSIONID=TEST_FIXATION_JAVA" 2>&1 | grep -i "jsessionid"
# Test format paramètre chemin
curl -sv "https://cible.com/login;jsessionid=TEST_PARAM_CHEMIN" 2>&1 | grep -i "jsessionid"BreachVex teste la livraison de session par paramètre URL dans le cadre de la suite de détection de fixation de session : chaque point de terminaison de connexion découvert est sondé avec ?PHPSESSID=, ?JSESSIONID=, ?session=, ?sid= et ?token= portant une valeur de test distinctive. Une valeur Set-Cookie correspondante confirme la livraison par URL.
<?php
// Option 1 : paramètres php.ini (préféré — application globale)
// session.use_only_cookies = 1 (rejeter les identifiants de session par URL)
// session.use_strict_mode = 1 (rejeter les identifiants fournis externalement)
// Option 2 : session_start() avec tableau d'options (PHP 7.0+)
session_start([
'use_only_cookies' => 1,
'use_strict_mode' => 1,
'cookie_httponly' => 1,
'cookie_secure' => 1,
'cookie_samesite' => 'Strict',
]);
// Après authentification : toujours régénérer
if (authenticate($email, $password)) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user_id;
}; php.ini — base de sécurité minimale des sessions
session.use_only_cookies = 1
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"
session.gc_maxlifetime = 1800 ; timeout inactivité 30 min
session.use_trans_sid = 0 ; désactiver l'identifiant de session transparent dans HTML<!-- web.xml — restreindre le suivi de session aux cookies uniquement -->
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="3.0">
<session-config>
<tracking-mode>COOKIE</tracking-mode>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>// Spring Boot — configuration de sécurité des sessions
@Configuration
public class SessionConfig {
@Bean
public DefaultCookieSerializer cookieSerializer() {
DefaultCookieSerializer s = new DefaultCookieSerializer();
s.setCookieName("__Host-session");
s.setUseHttpOnlyCookie(true);
s.setUseSecureCookie(true);
s.setSameSite("Strict");
s.setCookiePath("/");
// cookieDomain intentionnellement omis pour le préfixe __Host-
return s;
}
}express-session ne prend pas en charge la livraison de session par URL par défaut. La configuration ci-dessous applique un stockage Redis sécurisé et les attributs de cookie corrects :
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
name: '__Host-session', // préfixe __Host- empêche le tossing de sous-domaine
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS obligatoire
httpOnly: true, // inaccessible depuis JavaScript
sameSite: 'strict', // empêche la livraison cross-site
maxAge: 30 * 60 * 1000, // expiration inactivité 30 min
path: '/',
// domain : intentionnellement omis — préfixe __Host- exige l'absence de domain
},
}));
// À la connexion : toujours régénérer l'identifiant de session
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).json({ error: 'Erreur de session' });
req.session.userId = user.id;
res.redirect('/tableau-de-bord');
});
} else {
res.status(401).json({ error: 'Identifiants invalides' });
}
});
// express-session ne prend pas en charge la livraison de session par URL par défaut
// Aucune configuration supplémentaire n'est requise pour bloquer la livraison par URLNIST SP 800-63B §7.1.2 stipule explicitement : "Les secrets de session NE DOIVENT PAS être transmis dans des paramètres URL." C'est une exigence normative dans les directives d'identité numérique du NIST. Pour les applications commerciales, OWASP ASVS v5 V3.1.1 (Niveau 1) indique la même exigence : "jetons de session jamais révélés dans les URL, messages d'erreur ou journaux."
La fixation par paramètre URL se produit quand une application accepte un identifiant de session depuis la chaîne de requête URL — par exemple /login?PHPSESSID=VALEUR_ATTAQUANT ou /login?JSESSIONID=VALEUR_ATTAQUANT. L'attaquant construit une URL avec un identifiant pré-choisi, la livre à la victime, et quand la victime se connecte, le serveur réutilise cet identifiant comme jeton de session authentifié. Définir session.use_only_cookies=1 en PHP et le mode de suivi COOKIE dans les conteneurs de servlets Java élimine cela entièrement.
La spécification Java Servlet originale exigeait la réécriture URL comme mécanisme de secours pour les clients ne supportant pas les cookies. Servlet 3.0 a introduit l'annotation @SessionTrackingMode et <tracking-mode>COOKIE</tracking-mode> dans web.xml pour désactiver la réécriture URL. Java EE 7+ utilise par défaut COOKIE-only quand correctement configuré.
Quand session.use_only_cookies de PHP est défini à 0 (ancienne valeur par défaut dans les configurations PHP héritées), PHP acceptera un identifiant de session du paramètre URL PHPSESSID ainsi que depuis les cookies. Cela permet à toute URL avec ?PHPSESSID=VALEUR de définir la session pour cette application PHP. Un attaquant peut construire /login?PHPSESSID=VALEUR_CONNUE, partager l'URL avec une victime, et après l'authentification de la victime, utiliser le PHPSESSID connu pour accéder à la session authentifiée.
Les conteneurs de servlets Java prennent en charge deux formats de session basés sur URL : (1) paramètre chemin : /page;jsessionid=VALEUR_CONNUE (séparateur point-virgule dans le chemin), et (2) paramètre de requête : /page?jsessionid=VALEUR_CONNUE. Les deux sont des vecteurs de livraison équivalents. Le format ;jsessionid= est plus difficile à filtrer car il embarque l'identifiant dans le chemin avant la chaîne de requête, ce que certaines règles WAF manquent. Les deux sont désactivés en définissant COOKIE comme seul mode de suivi.
Quand un identifiant de session est embarqué dans l'URL, l'URL complète — incluant l'identifiant — apparaît dans l'en-tête Referer quand l'utilisateur navigue vers un lien externe. Les scripts d'analyse tiers, les boutons de partage sociaux, les ressources CDN, et tout contenu iframe reçoivent l'en-tête Referer contenant l'identifiant de session. C'est ainsi que les sessions embarquées dans URL fuient vers des tiers sans aucune activité d'attaque active. HackerOne #963569 (IBM) a démontré ce mécanisme exact.
CVE-2024-42346 a affecté Portainer CE/EE avant 2.21.0. Portainer acceptait des identifiants de session via des paramètres URL dans les URL de points de terminaison de gestion. Un attaquant pouvait construire une URL comme portainer.example.com/#!/auth?sessionId=CONNU et la partager avec un administrateur. Quand l'administrateur s'authentifiait via l'URL construite, l'identifiant connu devenait une session admin authentifiée, donnant un accès complet à la gestion des conteneurs.
session.use_strict_mode=1 (PHP 5.5.2+) fait rejeter par PHP les identifiants de session qui n'existent pas déjà dans le store de session — il n'acceptera pas un identifiant fourni externalement sans enregistrement côté serveur existant. Combiné avec session.use_only_cookies=1, il élimine à la fois la livraison par URL et l'acceptation d'identifiants arbitraires. Les deux doivent être définis ensemble.
La plupart des WAF peuvent être configurés pour bloquer ou supprimer PHPSESSID et JSESSIONID des paramètres de requête URL. Cependant, le filtrage par WAF est un contrôle secondaire — il ne corrige pas la vulnérabilité fondamentale dans l'application. Le format de paramètre chemin ;jsessionid= peut contourner les règles WAF qui ne vérifient que les paramètres de requête. La correction correcte est au niveau applicatif.
Apple Calendar Server exposait historiquement des identifiants de calendrier — qui servaient d'identifiants de session ou de ressources persistants — dans des paramètres URL. Quand des utilisateurs partageaient des liens de calendrier ou naviguaient vers du contenu externe depuis des applications de calendrier, le paramètre CALID dans l'URL fuitait via les en-têtes Referer vers des ressources tierces intégrées dans les descriptions d'événements de calendrier. C'est une trouvaille CWE-598.
session.use_only_cookies peut être défini dans php.ini (globalement) ou dans .htaccess (par répertoire). Il ne peut pas être défini via ini_set() à l'exécution après l'appel de session_start(). Pour les applications où modifier php.ini n'est pas possible, utiliser le tableau de paramètres session_start(['use_only_cookies' => 1]) (PHP 7.0+) fonctionne comme alternative.