E6 — Cybersécurité des services informatiques

Easy2Drive — SLAM, session 2022

Méthodologie et corrigé commenté — E6 — Cybersécurité des services informatiques, BTS SIO.

  • Durée 4h
  • Filière SLAM
  • Session 2022

Corrigé commenté Lockbay

Sujet officiel

Cas Easy2Drive — Session 2022, U6/E6 (SLAM), durée 4h, coef 4, sur 80 points (Dossier A : 30 pts, Dossier B : 25 pts, Dossier C : 25 pts). Code sujet : 22SI5SLAM. 📄 Télécharger le sujet officiel Easy2Drive (réseaucerta.org)

Lockbay n’héberge pas le sujet : ce corrigé est une production originale Lockbay qui répond aux questions du sujet officiel. Sujet © Éducation nationale.

Easy2Drive édite une plateforme de formation au code de la route (PHP/MySQL) utilisée par 6 000 auto-écoles et 500 000 élèves. À la suite d’un audit, vous assistez la DPO sur trois axes : sécuriser l’e-learning (DICP, RGPD, mots de passe), prendre en compte les conclusions de l’audit (Garantie Réussite, traçage RGPD) et mettre en œuvre des contre-mesures dans la gestion des avis (injection SQL, MVC). Corrigé question par question.

Dossier A — Sécurisation de l'application e-learning 30 pts

Mission A1 — Évaluation des risques à partir des récits utilisateurs

A.1.1

a) Disponibilité — récit 1 (**) vs récit 2 (*). Le récit 1 (consulter le site à titre d’information) concerne la vitrine publique : son indisponibilité fait perdre des prospects et de la visibilité commerciale → besoin important. Le récit 2 (changer son mot de passe) est une action ponctuelle, qu’on peut différer sans conséquence métier → besoin modéré.

b) Intégrité et confidentialité du récit 2 (**). Intégrité importante : un mot de passe altéré (corruption, troncature) rendrait le compte inaccessible ou affaibli. Confidentialité importante : le mot de passe est un secret d’authentification dont la divulgation permettrait l’usurpation du compte.

c) Preuve — récit 1 (-) vs récit 3 (**). Le récit 1 est une consultation anonyme sans compte : aucun acte à prouver. Le récit 3 (poster un avis) produit un contenu opposable : en cas de contestation (avis diffamatoire, litige), il faut prouver qui a posté quoi et quand → besoin important.

Mission A2 — Prise en compte du RGPD

A.2.1

Non-conformité des cookies (réglementation de septembre 2020)

La délibération CNIL du 17 septembre 2020 impose un consentement explicite : l’utilisateur doit être clairement informé des finalités des cookies et pouvoir choisir entre accepter, refuser ou paramétrer. Or le bandeau actuel se contente d’afficher « Nous utilisons des cookies pour nous assurer du bon fonctionnement de notre site » :

  • il n’indique pas les finalités précises ;
  • il ne propose ni refus ni paramétrage (consentement implicite) ;
  • l’usage du site vaudrait acceptation, ce qui n’est plus accepté.

Le bandeau est donc non conforme et expose Easy2Drive à une amende (sanctions applicables depuis le 1ᵉʳ avril 2021).

A.2.2

Données à caractère personnel dans le récit 1

Le récit 1 (consultation sans création de compte) collecte automatiquement (cf. politique de confidentialité) : adresse IP, type d’appareil, teneur des requêtes, version du navigateur, résolution d’écran, système d’exploitation et langue.

La donnée à caractère personnel est l’adresse IP : elle permet, directement ou indirectement (via le FAI), d’identifier une personne physique. C’est la définition d’une donnée personnelle au sens du RGPD (art. 4). Les autres informations (résolution, OS, navigateur…) ne permettent pas à elles seules d’identifier l’internaute.

Mission A3 — Vérification de la sécurité du mot de passe

A.3.1

a) Problèmes du mot de passe initial. Il est transmis en clair par courriel (Doc A4 : qamQdVD3 lisible) : le courriel n’est pas chiffré et peut être intercepté ou consulté par un tiers. De plus l’élève peut le conserver toute l’année sans le changer : un secret communiqué par un canal non sûr et jamais renouvelé reste compromis durablement.

b) Meilleure solution. Communiquer un mot de passe à usage unique / lien temporaire obligeant l’élève à définir lui-même son mot de passe à la première connexion (le mot de passe initial expire après usage). On évite ainsi qu’un secret durable circule par courriel.

A.3.2

a) Politique actuelle (lecture de verifPassword). La validation exige $points_total == $resultat avec $resultat = $points_long * $points_comp, et $points_total = 6. $points_long vaut 1 si longueur ≥ 8, 0 sinon. Pour atteindre 6, il faut donc $points_long = 1 et $points_comp = 6. La complexité rapporte +1 (minuscule), +2 (majuscule), +3 (chiffre) : 6 = 1+2+3. La politique actuelle impose donc : longueur ≥ 8 et présence d’au moins une minuscule, une majuscule et un chiffre. Le caractère spécial n’est pas exigé et la longueur minimale (8) est inférieure aux 12 caractères CNIL.

b) Modification pour respecter la CNIL (longueur ≥ 12 ; minuscule +1, majuscule +2, chiffre +3, caractère spécial +4 ; seuil ≥ 10). Parties à modifier/ajouter :

$points_total = 10;            // ligne 1 : seuil de validation ≥ 10
...
if ($longueur >= 12) { $points_long = 1; }   // ligne 5 : 12 caractères mini (CNIL)
...
// après le test du chiffre (ligne 8), ajouter le caractère spécial :
if (preg_match("/\W/", $mdp)) { $points_comp = $points_comp + 4; }

$resultat = $points_long * $points_comp;     // ligne 9 inchangée
return ($points_total <= $resultat);         // ligne 10 : ≥ au lieu de ==

Un mot de passe de 12 caractères contenant les 4 familles donne $points_comp = 1+2+3+4 = 10, donc $resultat = 1 × 10 = 10 ≥ 10 → validé.

c) Tests unitaires testVerifPassword mis à jour (avec la nouvelle politique, "qamQdVD3" — 8 caractères, sans spécial — n’est plus valide) :

public function testVerifPassword()
{
    // trop court (< 12) → false
    $this->assertSame(false, verifPassword("Qam3!"));
    // 12 car. mais pas de caractère spécial : 1*(1+2+3)=6 → false
    $this->assertSame(false, verifPassword("QamQdVD3abcd"));
    // 12 car., 4 familles : 1*(1+2+3+4)=10 → true
    $this->assertSame(true,  verifPassword("QamQdVD3abc!"));
    // longueur 14, 4 familles → true
    $this->assertSame(true,  verifPassword("Qam3!QdVDbcdEf"));
}
A.3.3

a) Ajout du champ dateMajMDP (obligatoire, initialisé à la création) :

ALTER TABLE Utilisateur ADD dateMajMDP DATE NOT NULL DEFAULT (CURRENT_DATE());

b) Fonction stockée renouvelleMDP (retourne vrai si le mot de passe a plus de 90 jours), sur le modèle du Doc commun 2 :

CREATE FUNCTION renouvelleMDP(num INT)
RETURNS BOOLEAN
BEGIN
    DECLARE v_date DATE;
    DECLARE v_retour BOOLEAN DEFAULT false;
    SELECT dateMajMDP INTO v_date FROM Utilisateur WHERE id = num;
    IF (DATE_ADD(v_date, INTERVAL 90 DAY) < CURRENT_DATE()) THEN
        SET v_retour = true;   -- mot de passe trop ancien : renouvellement requis
    END IF;
    RETURN v_retour;
END
Dossier B — Conclusions de l'audit 25 pts

Mission B1 — Attribution de la Garantie Réussite

B.1.1

Conséquence d’une déclaration abusive d’échec

Easy2Drive rembourse à tort les frais de présentation à un nouvel examen du code à un élève qui n’y avait pas droit (fausse déclaration d’échec par l’auto-école). La conséquence principale est donc une perte financière pour l’entreprise (remboursements indus, multipliés à l’échelle des 6 000 auto-écoles), assortie d’un risque de fraude organisée.

B.1.2

a) Analyse du déclencheur (Doc B2). Plusieurs conditions de la Garantie Réussite (Doc B1) sont mal ou pas implémentées :

Condition attendue (B1)État dans le triggerAnomalie
Échec daté de moins de 6 moisligne 12 : DATE_ADD(NEW.dateEtg, INTERVAL 6 MONTH) >= NOW() puis erreurTest inversé : il rejette les échecs récents alors qu’il faut rejeter les échecs anciens (> 6 mois)
≥ 25 séries de quizlignes 16-20 : WHERE idEleve = NEW.idNEW.id au lieu de NEW.idUtilisateur (clé de Eleve)
Moyenne ≥ 34/40 sur 4 meilleurs examenslignes 21-25 : idem NEW.idmême erreur de clé ; OK sur le LIMIT 4
≥ 4 examens blancs passésabsenteaucune vérification du nombre minimal d’examens blancs
Garantie accordée une seule fois (1ᵉʳ échec)absenterien n’empêche une seconde attribution
garantieReussite réellement positionnéaucune affectationle champ n’est jamais mis à TRUE à la fin

b) Code corrigé (parties à modifier/ajouter) :

DECLARE v_nbExam INT;

-- échec trop ANCIEN : on rejette si l'échec date de plus de 6 mois
IF DATE_ADD(NEW.dateEtg, INTERVAL 6 MONTH) < NOW() THEN
    SIGNAL SQLSTATE '10002'
        SET MESSAGE_TEXT = 'Garantie réussite : échec trop ancien';
END IF;

-- garantie déjà attribuée une fois
IF OLD.garantieReussite = TRUE THEN
    SIGNAL SQLSTATE '10006'
        SET MESSAGE_TEXT = 'Garantie réussite : déjà attribuée';
END IF;

-- séries de quiz : corriger la clé (idUtilisateur)
SELECT COUNT(*) INTO v_nbSerie FROM Evaluer WHERE idEleve = NEW.idUtilisateur;

-- au moins 4 examens blancs (condition manquante)
SELECT COUNT(*) INTO v_nbExam FROM Passer WHERE idEleve = NEW.idUtilisateur;
IF v_nbExam < 4 THEN
    SIGNAL SQLSTATE '10004'
        SET MESSAGE_TEXT = 'Garantie réussite : examens blancs insuffisants';
END IF;

-- moyenne des 4 meilleurs : corriger la clé
SELECT AVG(examenScore) INTO v_scoreMoyen FROM
   (SELECT examenScore FROM Passer WHERE idEleve = NEW.idUtilisateur
    ORDER BY examenScore DESC LIMIT 4) AS MeilleureNotes;

-- toutes les conditions OK : on attribue la garantie
SET NEW.garantieReussite = TRUE;

Mission B2 — Traçage des événements sur les données personnelles

B.2.1

Schéma de BD_RGPD_LOGS

L’entretien (Doc B3) impose de savoir, pour chaque table contenant des données personnelles, quel utilisateur, sous quel rôle, a réalisé quelle action, quand, et sur quel enregistrement. Schéma relationnel proposé :

Log (id, dateHeure, action, nomTable, idEnregistrement,
     idUtilisateur, nomUtilisateur, prenomUtilisateur, role)
Clé primaire : id
  • action ∈ { consultation, insertion, modification, suppression } ;
  • nomTable + idEnregistrement : la donnée concernée (ex. table Eleve, enreg. n°2348) ;
  • idUtilisateur, nom, prenom, role : l’auteur de l’action (rôle : directeur, formateur, élève, modérateur) ;
  • dateHeure : horodatage.

On dénormalise volontairement nom/prénom/rôle dans la table de logs : la base étant sur un serveur distinct, elle ne peut pas faire de jointure avec BD_EASY, et le journal doit rester figé même si l’utilisateur est ensuite modifié ou supprimé.

B.2.2

Création de l’utilisateur MySQL

L’application se connecte depuis le serveur web Easy2Drive.fr, donc l’hôte autorisé est ce nom DNS :

CREATE USER 'APPLI_RGPD_LOGS'@'Easy2Drive.fr' IDENTIFIED BY 'unMotDePasseFort';
B.2.3

Attribution de la permission (insertion seule)

Conformément au moindre privilège (CNIL : journaux inaltérables), on n’accorde que INSERT, sur la base BD_RGPD_LOGS :

GRANT INSERT ON BD_RGPD_LOGS.* TO 'APPLI_RGPD_LOGS'@'Easy2Drive.fr';

Pas de SELECT, UPDATE ni DELETE : l’application ne peut qu’ajouter des traces, jamais les lire ni les modifier — la lecture est réservée à la DPO via l’application « Logs RGPD ».

B.2.4

a) Durée de conservation. La CNIL (Doc CNIL 3) impose une période glissante n’excédant pas 6 mois pour ce type de journaux. On propose donc 6 mois.

b) Document où consigner cette durée. Le registre des activités de traitement exigé par l’art. 30 du RGPD (qui mentionne, pour chaque traitement, les durées de conservation prévues).

c) Purge automatique (solution technique sans la réaliser). Mettre en place un événement planifié MySQL (CREATE EVENT) s’exécutant périodiquement (ex. quotidiennement) pour supprimer les enregistrements de plus de 6 mois (DELETE FROM Log WHERE dateHeure < DATE_SUB(NOW(), INTERVAL 6 MONTH)). Alternative : une tâche planifiée système (cron) lançant le même script de purge.

Dossier C — Contre-mesures dans la gestion des avis 25 pts

Mission C1 — Saisie d’un avis par un élève

C1.1

Méthode getNbMaxAvisAtteint de la classe Eleve

L’élève ne peut plus saisir d’avis dès qu’il en a déposé 3 (commentaire : « tableau de 0 à 3 Avis ») :

public function getNbMaxAvisAtteint(): bool
{
    return count($this->lesAvis) >= 3;
}
C1.2

Méthode monAvis (condition de redirection)

Le formulaire ne doit être accessible que si l’élève est autorisé à saisir un avis. Il ne l’est plus dans deux cas : il a atteint le maximum de réécritures (3 avis), ou son dernier avis est en attente de modération (getModere() == false). On redirige donc vers l’accueil quand l’une de ces conditions est vraie :

if ( $user->getNbMaxAvisAtteint()
     || ( count($user->getLesAvis()) > 0
          && $user->getDernierAvis()->getModere() == false ) ) {
    // l'utilisateur est redirigé vers l'accueil
    return $this->redirectToRoute('home');
} else {
    // le formulaire est accessible
    ...
}
C1.3

a) Résultat de l’injection. La requête insertUnAvis (Doc C5) concatène le contenu sans échappement :

$req = "insert into avis(contenu,dateDepot,publie,modere,idEleve) values('"
     . $unAvis->getContenu() . "',now()," . ... . ")";

La charge injectée ferme la première valeur puis enchaîne plusieurs tuples VALUES (...). Résultat : plusieurs avis sont insérés en une seule requête, tous marqués publie=true, modere=true (3ᵉ et 4ᵉ valeurs) — donc directement publiés et considérés comme modérés, avec une note de 5/5. L’élève 12 réussit ainsi à publier en masse de faux avis négatifs déjà validés, sans passer par la modération.

b) Contournement des mesures précédentes. Les contre-mesures de la mission C1 (limite à 3 avis, blocage tant que non modéré) reposent sur le flux applicatif normal. L’injection SQL court-circuite la logique métier : elle écrit directement en base avec publie=true/modere=true, donc ni la modération ni le compteur d’avis ne sont sollicités. Les garde-fous PHP sont inopérants car la vulnérabilité est en aval, dans la couche d’accès aux données.

c) Correction (sans la réaliser). Utiliser des requêtes préparées paramétrées (PDO) : remplacer la concaténation par des marqueurs :contenu, :idEleve… liés via bindParam/bindValue, et fixer publie/modere côté serveur (toujours false à l’insertion). Le contenu saisi est alors traité comme une donnée, jamais comme du SQL :

$req = "insert into avis(contenu,dateDepot,publie,modere,idEleve)
        values(:contenu, now(), 0, 0, :idEleve)";
$res = PdoEasy2Drive::$monPdo->prepare($req);
$res->bindValue(':contenu', $unAvis->getContenu());
$res->bindValue(':idEleve', $unAvis->getLEleve()->getId());
$res->execute();

Mission C2 — La modération des avis

C2.1

Méthode getDoublonMail de PdoEasy2Drive

Renvoie vrai si l’adresse est partagée par plusieurs élèves (requête préparée) :

public function getDoublonMail($unEmail): bool
{
    $req = "select count(*) as nb from utilisateur
            join eleve on utilisateur.id = eleve.idUtilisateur
            where utilisateur.email = :email";
    $res = PdoEasy2Drive::$monPdo->prepare($req);
    $res->bindParam(':email', $unEmail);
    $res->execute();
    $ligne = $res->fetch();
    return $ligne['nb'] > 1;
}
C2.2

Méthode listeAvis (transmettre $doublonMail)

Dans la boucle foreach, on appelle getDoublonMail avec l’adresse de l’élève courant, puis on ajoute la clé doublonMail au tableau transmis à la vue :

foreach ($lesEleves as $unEleve) {
    if ($unEleve->getNeph() == null) {
        $pasDeNeph = true;
    } else {
        $pasDeNeph = false;
    }

    // détection du doublon d'adresse électronique
    $doublonMail = $PdoEasy2Drive->getDoublonMail($unEleve->getEmail());

    $tabDernierAvisParEleve[] = [
        'leEleve'     => $unEleve->getIdentite(),
        'avis'        => $unEleve->getDernierAvis(),
        'nbRefus'     => $unEleve->getNbAvisRefuse(),
        'pasDeNeph'   => $pasDeNeph,
        'doublonMail' => $doublonMail
    ];
}

Le modérateur dispose alors, pour chaque avis, des deux indices d’un faux élève : adresse en doublon et NEPH absent.

Points de barème (rappel)

DossierAttenduPoints
ADICP des récits (A1.1), RGPD cookies + données personnelles (A2), mot de passe CNIL en PHP + champ/fonction MySQL (A3)30
BConséquence + correction du trigger Garantie Réussite (B1), schéma BD_RGPD_LOGS, utilisateur/GRANT INSERT, conservation 6 mois + purge (B2)25
CgetNbMaxAvisAtteint + condition monAvis (C1), injection SQL et requêtes préparées (C1.3), getDoublonMail + listeAvis (C2)25
Total80
Sujet officiel © Éducation nationale — publié par le Réseau CERTA — Éducation nationale (lien source). Corrigé commenté et méthodologie © Lockbay Academy.