Aller au contenu principal

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 :

  1. quelle fonction PHP crée l'empreinte salée d'un mot de passe ?
  2. quelle fonction PHP vérifie une saisie par rapport à l'empreinte stockée ?
  3. 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

info

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.

attention

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
  1. Que répond la page de profil ?
  2. 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

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
  1. Quel est le nom du cookie, et que contient-il ?
  2. Parmi les attributs HttpOnly, Secure et SameSite, lesquels apparaissent dans la ligne Set-Cookie ?
  3. 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
  1. Que répond la page de profil ?
  2. De quoi l'attaquant a-t-il eu besoin ?
  3. 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.

  1. Que répond la seconde commande ?
  2. 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
  1. Quels attributs apparaissent maintenant dans la ligne Set-Cookie de la première commande ?
  2. Pour la deuxième commande, l'identifiant fixee-9999999999abcdef est-il conservé par le serveur ?
  3. 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
  1. Que répond la page de profil avant, puis après, la déconnexion ?
  2. 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.
astuce

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 :

  1. crée une session, avec un identifiant aléatoire, et stocke les données de l'utilisateur de son côté (dans $_SESSION) ;
  2. envoie cet identifiant au navigateur dans un cookie ;
  3. 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.

info

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.

AttributRôleEffet de sécurité
HttpOnlyInterdit au code JavaScript de lire le cookieLimite le vol par une faille XSS
SecureN'envoie le cookie qu'en HTTPSEmpê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 siteRé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​

AttaquePrincipeContre-mesure
VolL'attaquant lit l'identifiant (XSS, réseau non chiffré, poste partagé) puis le rejoueHttpOnly, Secure, HTTPS, correction des failles XSS
FixationL'attaquant impose un identifiant connu, puis attend la connexion de la victimeMode strict, session_regenerate_id(true) à la connexion
Rejeu après déconnexionUn identifiant déjà volé reste valable après la déconnexionsession_destroy(), expiration côté serveur
Identifiant dans l'URLL'identifiant se retrouve dans l'historique, les journaux, les liens partagéssession.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;
attention

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.

Pour aller plus loin

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​


Que contient le cookie de session d'un site PHP ?


Un attaquant récupère l'identifiant de session d'un utilisateur connecté. Que peut-il faire ?


À quoi sert l'attribut HttpOnly d'un cookie de session ?


Qu'est-ce qu'une attaque par fixation de session ?


À quel moment faut-il appeler session_regenerate_id(true) ?


Quel est l'effet de l'attribut Secure d'un cookie de session ?


Pourquoi supprimer le cookie dans le navigateur ne suffit-il pas à déconnecter un utilisateur ?


Pourquoi un site ne doit-il jamais transmettre l'identifiant de session dans l'adresse (URL) ?



Bonnes pratiques​

  • Considérer l'identifiant de session comme un mot de passe temporaire : qui le détient est l'utilisateur.
  • Activer HttpOnly, Secure et SameSite sur 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.