Le bracelet d'entrée qu'on peut copier
Vous venez de vous connecter (leçon 7) : vous avez saisi votre mot de passe, le serveur a vérifié votre identité une bonne fois. Mais comment s'en souvient-il à chaque requête suivante, sachant qu'HTTP n'a aucune mémoire ? C'est exactement le rôle de la session. Le serveur vous remet alors un cookie qui ressemble à ça :
Set-Cookie: PHPSESSID=kZ9f3aQ7tR2pX1mB
Ce PHPSESSID est votre bracelet d'entrée. À chaque requête, le navigateur le présente, et le serveur dit « ah, c'est l'admin ». Pratique. Mais voilà le problème : celui qui détient cette valeur EST l'admin. Pas besoin du mot de passe, le bracelet suffit.
Un attaquant qui glisse une XSS sur la page (leçon 4) lit ce cookie et se l'envoie. Il le colle ensuite dans son navigateur, recharge la page… et il est connecté en admin. C'est le vol de session (session hijacking) : on ne casse pas le mot de passe, on vole le bracelet qui prouve qu'on est déjà entré.
Cette leçon explique comment le serveur vous reconnaît d'une requête à l'autre, et comment protéger ce cookie. C'est lié à l'OWASP 2025 (Authentication Failures et Security Misconfiguration).
Ce qui transite vraiment
HTTP est sans mémoire : chaque requête arrive seule, le serveur ne sait pas que c'est encore vous. La session résout ça. À la connexion, le serveur garde vos données de son côté et vous donne un identifiant aléatoire rangé dans un cookie. Ensuite, ce cookie repart à chaque requête, et le serveur retrouve votre session grâce à lui.
Tout le secret tient donc dans cette petite valeur. Le mot de passe n'a transité qu'une fois, à la connexion ; après, c'est le cookie qui voyage en permanence. D'où la question qui compte : par où ce cookie peut-il fuiter ? Trois voies principales :
- Une XSS lit
document.cookieet exfiltre la valeur (leçon 4). - Le réseau : si le site est en HTTP (pas HTTPS), le cookie voyage en clair. N'importe qui sur le même Wi-Fi ouvert peut le lire.
- La fixation de session : l'attaquant impose un identifiant qu'il connaît déjà avant que vous vous connectiez, puis le réutilise.
Le faux remède : « je chiffre l'identifiant de session » ne sert à rien. L'attaquant n'a pas besoin de comprendre le cookie, il le recopie tel quel et le rejoue. Et « je mets l'identifiant dans l'URL » est pire : il fuite alors par l'historique, l'en-tête Referer et les logs. Ce qui protège, ce n'est pas le format du cookie, ce sont ses attributs et le HTTPS (section 4).
À vous d'attaquer : volez la session de l'admin
L'admin est connecté, son cookie de session existe. Vous, l'attaquant, allez le voler via une XSS puis le rejouer pour entrer en tant qu'admin. Essayez d'abord avec un cookie sans HttpOnly, puis activez HttpOnly et recommencez. Tout est simulé dans votre navigateur.
Ce qui vient de se passer
Sans HttpOnly, la XSS lit document.cookie et récupère PHPSESSID. Vous le rejouez, et le serveur vous prend pour l'admin : aucun mot de passe demandé, le cookie suffit à prouver l'identité.
Avec HttpOnly, document.cookie ne renvoie plus le cookie de session : le JavaScript ne le voit pas, il n'y a plus rien à voler, et le rejeu échoue. Le vol est coupé à la source. (Attention : HttpOnly bloque le vol du cookie, mais une XSS peut encore agir au nom de la victime sans le lire, voir leçon 4.)
Le correctif : les bons attributs du cookie
Un cookie de session se pose avec trois attributs qui ferment chacun une porte. Ce ne sont pas des options avancées, c'est la base.
- HttpOnly : le JavaScript ne peut pas lire le cookie. Une XSS ne peut donc plus le voler via
document.cookie. - Secure : le cookie n'est envoyé que sur HTTPS. Plus de cookie en clair reniflable sur le réseau.
- SameSite (
LaxouStrict) : le cookie n'est pas envoyé sur les requêtes venues d'un autre site. C'est la défense contre le CSRF (leçon 9).
En PHP, on règle tout ça avant de démarrer la session, et on régénère l'identifiant à la connexion :
// avant session_start : les attributs du cookie de session
session_set_cookie_params([
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
]);
session_start();
// à la connexion réussie : nouvel identifiant (contre la fixation de session)
session_regenerate_id(true);
La régénération est importante : elle donne un nouvel identifiant au moment où l'utilisateur passe d'anonyme à connecté. Si un attaquant avait imposé un identifiant connu d'avance, celui-ci devient inutile. À la déconnexion, on détruit la session côté serveur (session_destroy()), on ne se contente pas d'oublier le cookie.
HttpOnly n'est pas un correctif de la XSS. Il empêche le vol du cookie, mais une XSS active peut toujours agir depuis le navigateur de la victime (changer l'email, lancer une action) sans jamais lire le cookie. HttpOnly limite l'impact, il ne remplace pas l'encodage en sortie de la leçon 4. On empile les couches.
Défense en profondeur :
- HttpOnly + Secure + SameSite sur le cookie de session (et le préfixe
__Host-si possible) ; - régénérer l'identifiant à la connexion et à tout changement de privilège ;
- timeout d'inactivité + timeout absolu, et déconnexion = destruction côté serveur ;
- identifiant long et aléatoire : laissez le framework le générer, ne l'inventez pas ;
- HTTPS partout (plus HSTS), jamais d'identifiant de session dans l'URL.
Référence : OWASP Session Management Cheat Sheet.
La méthode et l'arsenal du pentester
Maintenant qu'on sait comment fermer les portes, voyons comment le pentester les teste une par une.
1. Récupérer un cookie de session. Par une XSS (document.cookie exfiltré, leçon 4), par le réseau (un site en HTTP sur un Wi-Fi ouvert), ou parce qu'un cookie traîne en clair dans des logs ou une URL.
2. Le rejouer. C'est l'étape la plus simple : on colle la valeur volée dans les outils de développement du navigateur (Application → Cookies), on recharge, et on est la victime. L'onglet « Match & Replace » de Burp fait pareil automatiquement.
3. La fixation de session. Si le site garde le même identifiant avant et après la connexion, l'attaquant en impose un qu'il connaît (par un lien, un sous-domaine), attend que la victime se connecte, et réutilise cet identifiant désormais authentifié.
L'arsenal.
- Burp Suite : intercepter, inspecter et rejouer les cookies, repérer un identifiant qui ne change pas à la connexion.
- Les devtools du navigateur : coller un cookie volé prend dix secondes (onglet Application). L'arme la plus simple du vol de session.
- Wireshark / bettercap : renifler le trafic réseau, là où un site est resté en HTTP.
L'impact, et le cas des tokens
Voler une session, c'est devenir la victime sans son mot de passe. Pire : ça contourne même le MFA. Le deuxième facteur a été demandé une seule fois, à la connexion. Une fois la session ouverte, le cookie seul suffit. L'attaquant hérite d'un accès déjà authentifié, et c'est silencieux : aucune tentative de connexion ratée, rien dans les alertes.
Le même piège existe avec les tokens (JWT) des applications modernes. Beaucoup les rangent dans le localStorage « pour la simplicité ». Mauvais réflexe : le localStorage est lisible par n'importe quel JavaScript de la page. Une XSS y prend donc le token instantanément, exactement comme un cookie sans HttpOnly. Un token volé se rejoue comme un cookie volé. La règle reste la même : le secret de session ne doit jamais être lisible par le JavaScript. Concrètement, un cookie HttpOnly.
Ce que ça révèle côté défense : le cookie de session mérite autant de soin que le mot de passe, parce qu'il est l'identité une fois connecté. On empile : HttpOnly (le JS ne le lit pas), Secure + HTTPS (il ne fuite pas sur le réseau), SameSite (pas de CSRF), régénération + timeouts (une session volée ne vaut pas éternellement). Une seule couche ne suffit jamais (leçon 2).
La checklist sessions et cookies
À vérifier sur chaque cookie de session.
- Attributs :
HttpOnly,Secure,SameSite=Lax(ouStrict), préfixe__Host-si possible. - Identifiant : long, aléatoire, généré par le framework. Jamais dans l'URL.
- Cycle de vie : régénérer à la connexion, timeout d'inactivité + absolu, déconnexion = destruction côté serveur.
- Transport : HTTPS partout, plus HSTS.
- Tokens (JWT) : dans un cookie
HttpOnly, jamais danslocalStorage.
Les références. La OWASP Session Management Cheat Sheet couvre tout (attributs, régénération, timeouts). Pour s'entraîner légalement : les labs « Authentication » et la section cookies de la Web Security Academy.
Rappel. Voler et rejouer la session de quelqu'un d'autre est un accès non autorisé, même « pour voir ». On ne teste que ses propres systèmes ou une cible explicitement autorisée (leçon 1).
The entry wristband you can copy
You've just logged in (lesson 7): you typed your password, the server verified your identity once. But how does it remember you on every subsequent request, given that HTTP has no memory? That's exactly what a session is for. The server then hands you a cookie that looks like this:
Set-Cookie: PHPSESSID=kZ9f3aQ7tR2pX1mB
This PHPSESSID is your entry wristband. On every request, the browser shows it, and the server says "ah, it's the admin". Handy. But here's the problem: whoever holds this value IS the admin. No password needed, the wristband is enough.
An attacker who slips an XSS onto the page (lesson 4) reads this cookie and sends it to themselves. They then paste it into their browser, reload the page… and they're logged in as admin. That's session hijacking: you don't crack the password, you steal the wristband that proves you already got in.
This lesson explains how the server recognizes you from one request to the next, and how to protect that cookie. It maps to OWASP 2025 (Authentication Failures and Security Misconfiguration).
What really travels
HTTP is stateless: each request arrives alone, the server doesn't know it's still you. The session solves that. At login, the server keeps your data on its side and gives you a random identifier stored in a cookie. After that, this cookie goes back on every request, and the server finds your session thanks to it.
So the whole secret is in that little value. The password traveled only once, at login; after that, it's the cookie that travels constantly. Hence the question that matters: where can this cookie leak? Three main paths:
- An XSS reads
document.cookieand exfiltrates the value (lesson 4). - The network: if the site is on HTTP (not HTTPS), the cookie travels in clear. Anyone on the same open Wi-Fi can read it.
- Session fixation: the attacker forces an identifier they already know before you log in, then reuses it.
Beware the false cure: "I'll encrypt the session identifier" is useless. The attacker doesn't need to understand the cookie, they copy it as-is and replay it. And "I'll put the identifier in the URL" is worse: it then leaks through history, the Referer header and logs. What protects you isn't the cookie's format, it's its attributes and HTTPS (section 4).
Your turn to attack: steal the admin's session
The admin is logged in, their session cookie exists. You, the attacker, will steal it via an XSS then replay it to get in as admin. Try first with a cookie without HttpOnly, then turn HttpOnly on and try again. Everything is simulated in your browser.
What just happened
Without HttpOnly, the XSS reads document.cookie and grabs PHPSESSID. You replay it, and the server takes you for the admin: no password asked, the cookie alone proves the identity.
With HttpOnly, document.cookie no longer returns the session cookie: JavaScript can't see it, there's nothing left to steal, and the replay fails. The theft is cut at the source. (Note: HttpOnly blocks stealing the cookie, but an XSS can still act as the victim without reading it, see lesson 4.)
The fix: the right cookie attributes
A session cookie is set with three attributes that each close a door. These aren't advanced options, they're the baseline.
- HttpOnly: JavaScript can't read the cookie. So an XSS can no longer steal it via
document.cookie. - Secure: the cookie is only sent over HTTPS. No more clear-text cookie sniffable on the network.
- SameSite (
LaxorStrict): the cookie isn't sent on requests coming from another site. That's the defense against CSRF (lesson 9).
In PHP, you set all of this before starting the session, and you regenerate the identifier at login:
// before session_start: the session cookie attributes
session_set_cookie_params([
'httponly' => true,
'secure' => true,
'samesite' => 'Lax',
]);
session_start();
// on successful login: a new identifier (against session fixation)
session_regenerate_id(true);
Regeneration matters: it gives a new identifier the moment the user goes from anonymous to logged in. If an attacker had forced a known identifier, it becomes useless. At logout, you destroy the session on the server (session_destroy()), you don't just forget the cookie.
HttpOnly is not an XSS fix. It stops the cookie from being stolen, but an active XSS can still act from the victim's browser (change the email, trigger an action) without ever reading the cookie. HttpOnly limits the impact, it doesn't replace the output encoding from lesson 4. You stack the layers.
Defense in depth:
- HttpOnly + Secure + SameSite on the session cookie (and the
__Host-prefix if possible); - regenerate the identifier at login and on any privilege change;
- idle timeout + absolute timeout, and logout = destruction on the server;
- long, random identifier: let the framework generate it, don't invent it;
- HTTPS everywhere (plus HSTS), never a session identifier in the URL.
Reference: OWASP Session Management Cheat Sheet.
The pentester's method and arsenal
Now that we know how to close the doors, let's see how the pentester tests them one by one.
1. Get a session cookie. Via an XSS (document.cookie exfiltrated, lesson 4), via the network (an HTTP site on an open Wi-Fi), or because a cookie sits in clear in some logs or a URL.
2. Replay it. This is the easiest step: you paste the stolen value into the browser's developer tools (Application → Cookies), reload, and you're the victim. Burp's "Match & Replace" tab does the same automatically.
3. Session fixation. If the site keeps the same identifier before and after login, the attacker forces one they know (via a link, a subdomain), waits for the victim to log in, and reuses that now-authenticated identifier.
The arsenal.
- Burp Suite: intercept, inspect and replay cookies, spot an identifier that doesn't change at login.
- The browser devtools: pasting a stolen cookie takes ten seconds (Application tab). The simplest session-theft weapon.
- Wireshark / bettercap: sniff network traffic, where a site stayed on HTTP.
The impact, and the case of tokens
Stealing a session means becoming the victim without their password. Worse: it even bypasses MFA. The second factor was asked once, at login. Once the session is open, the cookie alone is enough. The attacker inherits an already-authenticated access, and it's silent: no failed login attempts, nothing in the alerts.
The same trap exists with the tokens (JWT) of modern apps. Many store them in localStorage "for simplicity". Bad reflex: localStorage is readable by any JavaScript on the page. So an XSS grabs the token instantly, exactly like a cookie without HttpOnly. A stolen token replays like a stolen cookie. The rule stays the same: the session secret must never be readable by JavaScript. Concretely, a HttpOnly cookie.
What this reveals for defense: the session cookie deserves as much care as the password, because it is the identity once logged in. You stack: HttpOnly (JS can't read it), Secure + HTTPS (it doesn't leak on the network), SameSite (no CSRF), regeneration + timeouts (a stolen session isn't valid forever). One layer is never enough (lesson 2).
The sessions & cookies checklist
To check on every session cookie.
- Attributes:
HttpOnly,Secure,SameSite=Lax(orStrict),__Host-prefix if possible. - Identifier: long, random, generated by the framework. Never in the URL.
- Lifecycle: regenerate at login, idle + absolute timeout, logout = destruction on the server.
- Transport: HTTPS everywhere, plus HSTS.
- Tokens (JWT): in a
HttpOnlycookie, never inlocalStorage.
The references. The OWASP Session Management Cheat Sheet covers it all (attributes, regeneration, timeouts). To practice legally: the "Authentication" labs and the cookies section of the Web Security Academy.
Reminder. Stealing and replaying someone else's session is unauthorized access, even "just to see". Only test your own systems or an explicitly authorized target (lesson 1).
Deux serveurs posent le cookie de session. L'un fait setcookie('PHPSESSID', $id) tout court. L'autre ajoute httponly, secure et samesite. Avant de dérouler : sur lequel une XSS de la page peut-elle voler la session via document.cookie ?
Voir la réponse
Sur le premier. Sans HttpOnly, le cookie est lisible en JavaScript : une XSS fait document.cookie, récupère le PHPSESSID et l'exfiltre. Sur le second, HttpOnly rend le cookie invisible au JavaScript (vol par XSS coupé), Secure empêche son envoi en clair sur le réseau, et SameSite bloque les requêtes cross-site (CSRF). Les trois attributs ferment trois portes différentes.
Two servers set the session cookie. One does just setcookie('PHPSESSID', $id). The other adds httponly, secure and samesite. Before you expand: on which one can a page XSS steal the session via document.cookie?
Show the answer
On the first. Without HttpOnly, the cookie is readable in JavaScript: an XSS calls document.cookie, grabs the PHPSESSID and exfiltrates it. On the second, HttpOnly makes the cookie invisible to JavaScript (XSS theft cut off), Secure stops it from being sent in clear over the network, and SameSite blocks cross-site requests (CSRF). The three attributes close three different doors.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Avec tes mots : pourquoi voler le cookie de session suffit pour devenir la victime (sans mot de passe), et que bloque chacun des attributs HttpOnly, Secure et SameSite ?
🧠 Rappel libre
Sans remonter : dis ce que contient le cookie de session et pourquoi le voler suffit, cite deux façons de le voler, et explique la différence entre HttpOnly et Secure.
document.cookie, ou le reniflage réseau si le site est en HTTP (cookie en clair). HttpOnly empêche le JavaScript de lire le cookie (contre le vol par XSS) ; Secure empêche son envoi en clair en n'autorisant que HTTPS (contre le reniflage). Les deux sont complémentaires, pas interchangeables.⚖️ Juge le code de l'IA
Tu construis une application avec une API et un front en JavaScript. L'IA propose : « Après la connexion, je renvoie un token JWT et je le range dans localStorage : c'est plus pratique, ça survit au rechargement de la page et je l'ajoute facilement à chaque appel d'API. » Tu acceptes, ou tu rejettes ?
localStorage est lisible par n'importe quel JavaScript de la page. La moindre XSS y lit le token instantanément, exactement comme un cookie sans HttpOnly : un token volé se rejoue et l'attaquant devient la victime, sans mot de passe. Le « pratique » se paie cher. La bonne approche : ranger le secret de session dans un cookie HttpOnly (donc invisible au JavaScript), avec Secure et SameSite. Le navigateur l'enverra tout seul à chaque requête, sans que ton code y touche, et une XSS ne pourra pas le lire. La commodité de localStorage ne vaut pas l'ouverture au vol de session.document.cookie ?Secure sur un cookie ?You protect the session cookie. But SameSite, only mentioned here, deserves its own lesson: lesson 9 attacks CSRF, making the victim act without knowing, and the real defense, the anti-CSRF token.
Lesson 9: Cross-site request forgery (CSRF) →