Sessions et cookies PHP : les sécuriser contre le vol de session
Vous vous êtes connecté une seule fois, et le site vous reconnaît à chaque page. Que se passe-t-il si quelqu'un d'autre présente la même « preuve » que vous ?
Petite vérification
Avant de commencer, vérifiez si vous savez encore répondre à ces 3 questions très simples, sur la séance précédente consacrée au stockage des mots de passe en PHP :
- quelle fonction PHP crée l'empreinte salée d'un mot de passe ?
- quelle fonction PHP vérifie une saisie par rapport à l'empreinte stockée ?
- dans une empreinte qui commence par
$2y$12$, quel est l'algorithme utilisé, et quel est le coût de calcul ?
📌 Réponse attendue
TP pour réfléchir et résoudre des problèmes
Votre défi pour aujourd'hui consiste à voler une session sans connaître le mot de passe de son propriétaire, à la rejouer, puis à corriger le site pour que ce vol ne fonctionne plus
Le club photo « Objectif 35 », son site et son adhérente Alice sont fictifs, construits pour l'exercice. Tout se passe sur votre propre VM, avec le serveur web intégré de PHP.
Rejouer la session d'un utilisateur pour se faire passer pour lui, sur un vrai site, est un accès frauduleux (article 323-1 du code pénal). Ce TP se pratique uniquement sur le site de votre VM.
Le club photo « Objectif 35 » dispose d'un petit site réservé à ses membres : après la connexion, une page de profil affiche « Bonjour » suivi du nom de l'adhérent. Vous êtes chargé(e) d'en vérifier la sécurité, en vous plaçant d'abord du côté de l'attaquant.
Étape 1 — Créer le site et le lancer
Dans le terminal de votre VM Debian, installez PHP et curl, puis créez le site :
sudo apt install php-cli curl
mkdir -p ~/tp-session/site
cd ~/tp-session
cat > site/login.php <<'EOF'
<?php
session_start();
$hash = '$2y$10$DQ7vy6C72zrKVhz5Y0.DGO5EjwQCmOhRx/i7qR8bz8ePds9HpDfQO';
if (($_POST['login'] ?? '') === 'alice' && password_verify($_POST['mdp'] ?? '', $hash)) {
$_SESSION['user'] = 'alice';
echo "Connexion réussie\n";
} else {
http_response_code(401);
echo "Identifiants incorrects\n";
}
EOF
cat > site/profil.php <<'EOF'
<?php
session_start();
if (isset($_SESSION['user'])) {
echo "Bonjour ", $_SESSION['user'], "\n";
} else {
http_response_code(403);
echo "Accès refusé\n";
}
EOF
Lancez le serveur en arrière-plan, puis demandez la page de profil sans être connecté :
php -S 127.0.0.1:8000 -t site > /dev/null 2>&1 &
curl -s http://127.0.0.1:8000/profil.php
- Que répond la page de profil ?
- Comment le site peut-il savoir si une personne est connectée, alors que le protocole HTTP ne garde aucun souvenir d'une requête à l'autre ?
📌 Réponse attendue
Étape 2 — Se connecter et examiner le cookie
Connectez-vous en tant qu'Alice, en enregistrant les cookies reçus dans un fichier, puis consultez la page de profil avec ce cookie :
curl -si -c cookies.txt -d 'login=alice&mdp=azerty123' http://127.0.0.1:8000/login.php
curl -s -b cookies.txt http://127.0.0.1:8000/profil.php
- Quel est le nom du cookie, et que contient-il ?
- Parmi les attributs
HttpOnly,SecureetSameSite, lesquels apparaissent dans la ligneSet-Cookie? - Où sont stockées les informations d'Alice (son nom, sa connexion) : dans le cookie, ou ailleurs ?
📌 Réponse attendue
Étape 3 — Voler la session
Vous jouez maintenant l'attaquant. Il n'a pas le mot de passe d'Alice, mais il a réussi à lire son identifiant de session (script malveillant, réseau non chiffré, poste partagé...). Rejouez-le :
SID=$(awk '/PHPSESSID/ {print $7}' cookies.txt)
echo "$SID"
curl -s -b "PHPSESSID=$SID" http://127.0.0.1:8000/profil.php
- Que répond la page de profil ?
- De quoi l'attaquant a-t-il eu besoin ?
- Comment un attaquant pourrait-il, en vrai, obtenir cet identifiant ?
📌 Réponse attendue
Étape 4 — Imposer un identifiant : la fixation de session
Autre attaque : au lieu de voler l'identifiant, l'attaquant en choisit un et pousse la victime à s'en servir (par exemple avec un lien). La victime se connecte ensuite avec cet identifiant. Simulez-le :
curl -s -b "PHPSESSID=fixee-1234567890abcdef" -d 'login=alice&mdp=azerty123' http://127.0.0.1:8000/login.php
curl -s -b "PHPSESSID=fixee-1234567890abcdef" http://127.0.0.1:8000/profil.php
La première commande représente Alice qui se connecte avec l'identifiant imposé. La seconde, l'attaquant, qui connaît cet identifiant.
- Que répond la seconde commande ?
- Quelles deux faiblesses du site rendent cette attaque possible ?
📌 Réponse attendue
Étape 5 — Corriger la connexion
Créez une version corrigée de la page de connexion : elle protège le cookie, refuse les identifiants inconnus et renouvelle l'identifiant une fois la connexion réussie :
cat > site/login_secure.php <<'EOF'
<?php
ini_set('session.use_strict_mode', '1');
session_set_cookie_params(['httponly' => true, 'samesite' => 'Lax']);
session_start();
$hash = '$2y$10$DQ7vy6C72zrKVhz5Y0.DGO5EjwQCmOhRx/i7qR8bz8ePds9HpDfQO';
if (($_POST['login'] ?? '') === 'alice' && password_verify($_POST['mdp'] ?? '', $hash)) {
session_regenerate_id(true);
$_SESSION['user'] = 'alice';
echo "Connexion réussie\n";
} else {
http_response_code(401);
echo "Identifiants incorrects\n";
}
EOF
curl -si -c cookies2.txt -d 'login=alice&mdp=azerty123' http://127.0.0.1:8000/login_secure.php
curl -si -b "PHPSESSID=fixee-9999999999abcdef" -d 'login=alice&mdp=azerty123' http://127.0.0.1:8000/login_secure.php
curl -s -b "PHPSESSID=fixee-9999999999abcdef" http://127.0.0.1:8000/profil.php
- Quels attributs apparaissent maintenant dans la ligne
Set-Cookiede la première commande ? - Pour la deuxième commande, l'identifiant
fixee-9999999999abcdefest-il conservé par le serveur ? - Que répond la troisième commande, et pourquoi l'attaque de l'étape 4 échoue-t-elle ?
📌 Réponse attendue
Étape 6 — Se déconnecter vraiment
Que se passe-t-il quand un utilisateur clique sur « Déconnexion » ? Créez la page, puis rejouez un identifiant après la déconnexion :
cat > site/logout.php <<'EOF'
<?php
session_start();
$_SESSION = [];
session_destroy();
setcookie(session_name(), '', time() - 3600, '/');
echo "Déconnecté\n";
EOF
SID2=$(awk '/PHPSESSID/ {print $7}' cookies2.txt)
curl -s -b "PHPSESSID=$SID2" http://127.0.0.1:8000/profil.php
curl -s -b "PHPSESSID=$SID2" http://127.0.0.1:8000/logout.php
curl -s -b "PHPSESSID=$SID2" http://127.0.0.1:8000/profil.php
- Que répond la page de profil avant, puis après, la déconnexion ?
- Pourquoi faut-il appeler
session_destroy(), et pas seulement supprimer le cookie du navigateur ?
Quand vous avez terminé, arrêtez le serveur :
pkill -f "php -S"
📌 Réponse attendue
Étape 7 — Conseiller le club
Le président du club vous demande : « Le site va passer en ligne pour de bon. Que doit-on encore faire pour que les sessions de nos adhérents soient protégées ? » Que lui répondez-vous ?
Restitution orale
Un étudiant (ou un binôme) vient présenter à la classe les deux attaques réalisées (étapes 3 et 4), les corrections apportées (étapes 5 et 6) et les conseils donnés au club (étape 7).
- Durée : 5 minutes de présentation + 2 à 3 minutes de questions.
- Qui passe : un(e) étudiant(e) (ou un binôme) désigné(e) par l'enseignant.
- Support : le travail réalisé pendant le TP, aucune préparation supplémentaire n'est nécessaire.
- Ce qui est attendu :
- rappeler le contexte (le site du club et le rôle du cookie de session) ;
- présenter la démarche suivie (vol, fixation, corrections) et les conseils donnés ;
- répondre aux questions de la classe.
Cet exercice entraîne à l'oral technique court exigé par plusieurs épreuves du BTS (notamment E5 et E6) : clarté, structure, capacité à répondre à l'improviste.
📌 Une solution
Notions théoriques
Vous venez de voler une session, d'en imposer une, puis de corriger le site. Voyons ce que sont une session et un cookie, et comment les protéger.
Pourquoi une session ?
Le protocole HTTP est sans mémoire : chaque requête est indépendante de la précédente. Pour reconnaître un utilisateur d'une page à l'autre, le serveur :
- crée une session, avec un identifiant aléatoire, et stocke les données de l'utilisateur de
son côté (dans
$_SESSION) ; - envoie cet identifiant au navigateur dans un cookie ;
- retrouve la session à chaque requête, grâce au cookie renvoyé par le navigateur.
Le cookie de session joue le rôle d'un ticket de vestiaire : celui qui le présente récupère le manteau, qu'il en soit le propriétaire ou non.
Le cookie ne contient que l'identifiant. Les données de la session (utilisateur connecté, panier...) restent sur le serveur, ce qui est plus sûr que de les confier au navigateur.
Les attributs d'un cookie de session
| Attribut | Rôle | Effet de sécurité |
|---|---|---|
| HttpOnly | Interdit au code JavaScript de lire le cookie | Limite le vol par une faille XSS |
| Secure | N'envoie le cookie qu'en HTTPS | Empêche la lecture par écoute du réseau |
SameSite (Lax ou Strict) | Limite l'envoi du cookie lors de requêtes issues d'un autre site | Réduit les requêtes forgées depuis un autre site |
Durée (lifetime, Expires) | Fixe combien de temps le cookie est conservé | Limite la fenêtre d'exploitation d'un cookie volé |
En PHP, on les règle avec session_set_cookie_params(), avant session_start().
Les attaques sur la session
| Attaque | Principe | Contre-mesure |
|---|---|---|
| Vol | L'attaquant lit l'identifiant (XSS, réseau non chiffré, poste partagé) puis le rejoue | HttpOnly, Secure, HTTPS, correction des failles XSS |
| Fixation | L'attaquant impose un identifiant connu, puis attend la connexion de la victime | Mode strict, session_regenerate_id(true) à la connexion |
| Rejeu après déconnexion | Un identifiant déjà volé reste valable après la déconnexion | session_destroy(), expiration côté serveur |
| Identifiant dans l'URL | L'identifiant se retrouve dans l'historique, les journaux, les liens partagés | session.use_only_cookies, jamais dans l'adresse |
La session n'est pas une authentification : c'est la preuve qu'une authentification a eu lieu. Celui qui détient cette preuve n'a plus besoin du mot de passe, ni de la double authentification.
Un squelette de code à retenir
<?php
ini_set('session.use_strict_mode', '1');
session_set_cookie_params([
'httponly' => true,
'secure' => true, // en HTTPS uniquement
'samesite' => 'Lax',
]);
session_start();
// ... après vérification du mot de passe avec password_verify() :
session_regenerate_id(true);
$_SESSION['user'] = $login;
Cette configuration protège l'identifiant de session, pas le site : une faille XSS permet toujours d'agir au nom de l'utilisateur depuis son navigateur. Les failles du site doivent être corrigées en parallèle.
Les séances précédentes de la progression ont montré comment un identifiant peut circuler sur un réseau
non chiffré (sniffing, homme du milieu). HTTPS et l'attribut Secure sont la réponse directe à ces
attaques pour les cookies de session.
Test de mémorisation/compréhension
Bonnes pratiques
- Considérer l'identifiant de session comme un mot de passe temporaire : qui le détient est l'utilisateur.
- Activer
HttpOnly,SecureetSameSitesur le cookie de session. - Servir le site en HTTPS, pour que l'identifiant ne circule jamais en clair.
- Renouveler l'identifiant (
session_regenerate_id(true)) après la connexion et à chaque changement de privilège. - Activer le mode strict et ne jamais accepter un identifiant de session dans l'adresse.
- Détruire la session côté serveur à la déconnexion, et limiter sa durée de vie.
- Corriger aussi les failles du site (XSS en tête) : les protéger ne suffit pas.