Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1523 minIntermédiaire

E7 SLAM : méthode et sujet corrigé

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • repérer les familles de questions de sécurité de l'E7 ;
  • répondre avec la méthode repérer, qualifier, corriger, justifier ;
  • éviter les pièges de vocabulaire et de SQL ;
  • traiter un cas complet dans les conditions de l'épreuve.

5 exercices corrigés

L'écrit E7 « Cybersécurité des services informatiques » de l'option SLAM est une étude de cas de 4 heures : un contexte d'organisation, des documents (extraits de code, schémas de base, journaux, textes juridiques) et des questions regroupées en dossiers. Ce chapitre montre comment la sécurité applicative y est interrogée, donne une méthode en quatre temps pour répondre, les pièges à éviter, puis un cas complet corrigé sur AppliFrais.

Comment la sécurité applicative tombe à l'E7

Les sujets de 2024 (alors épreuve E6, unité U6) et de 2025 donnent une bonne idée des questions. On y retrouve, d'une session à l'autre, les mêmes familles :

Famille de questionsExemples tirés des sujets récentsChapitre
Analyse de risquesFixer et justifier le niveau de besoin d'un critère DICP ; impacts d'une attaque ; mesures contre un rançongiciel1 ; sauvegardes contre le rançongiciel : cours Sauvegarde et PRA
Protection des mots de passe et des donnéesPourquoi ne pas stocker un mot de passe en clair ; hachage ou chiffrement ; chiffrer une colonne sensible6
Contrôle d'accès dans le codeÉcrire une méthode qui vérifie qu'un utilisateur possède un droit sur une table ; lire des tests unitaires de cette méthode3
Droits en base de donnéesCréer un compte, accorder les droits d'une matrice, créer une vue qui restreint l'accèscours SQL
JournalisationAjouter l'adresse IP à une méthode de journalisation ; vérifier qu'une table de journal respecte les recommandations de la CNIL ; requêtes SQL d'analyse des journaux12
Triggers d'auditTrouver pourquoi un trigger d'historique ne fonctionne pas, le corrigercours SQL et procédures et triggers
API RESTDangers des méthodes PUT et DELETE ; sécuriser une méthode avec un contrôle de droits et le bon code HTTP ; séparer une API publique en lecture seule13
AuthentificationMot de passe oublié : failles du processus et processus sécurisé ; SSO et jetons ; comparer un code PIN et un mot de passe11
Application mobileBiométrie et RGPD ; journal des tentatives dans une base SQLite13
DroitAccès frauduleux (Code pénal), RGPD (violation de données, données sensibles, durée de conservation), charte informatique1

Les corrigés détaillés de ces sujets sont sur le blog : sujet 2025 SLAM et sujet 2024 SLAM. Le cours Épreuve E7 SLAM va plus loin sur la méthode, avec les annales classées par thème et deux épreuves blanches ; le récapitulatif Quoi réviser pour l'E7 SLAM et le glossaire de l'E7 SLAM complètent ce chapitre.

Les langages des extraits de code varient d'un sujet à l'autre (Java, PHP, C#, JavaScript, SQL) : on attend que tu lises du code dans un langage courant et que tu écrives quelques lignes correctes, pas que tu maîtrises chaque bibliothèque. Une syntaxe approximative mais une logique juste rapporte plus qu'une syntaxe parfaite sur une mauvaise idée.

La méthode en quatre temps

Pour toute question qui présente un code, un processus ou une situation à sécuriser :

  1. Repérer. Lis le document ligne par ligne avec la grille du Top 10 (chapitre 2). Note la ligne exacte : « ligne 7, la saisie $_POST['login'] est concaténée dans la requête ».
  2. Qualifier. Nomme la faille (injection SQL, IDOR, XSS stockée…), le critère DICP atteint et l'impact dans le contexte du sujet : « un visiteur pourrait lire les RIB de ses collègues : atteinte à la confidentialité de données personnelles, violation à notifier à la CNIL ».
  3. Corriger. Écris la correction concrète, dans le langage du document : la requête préparée, la condition ajoutée, le middleware. Une contre-mesure générale (« sécuriser le code ») ne rapporte rien.
  4. Justifier. Explique en une phrase pourquoi la correction fonctionne, en t'appuyant sur un principe : « la requête préparée transmet la valeur séparément, elle ne peut plus modifier la structure de la requête ».

Rédiger une réponse qui rapporte des points

À la question « Expliquez le risque présenté par la ligne 4 et proposez une correction », compare deux réponses.

Réponse faible : « Il y a une faille de sécurité. Il faut sécuriser la requête et vérifier les données. »

Rien n'est faux, mais rien n'est précis : ni la faille, ni le risque, ni la correction.

Réponse attendue : « La ligne 4 concatène $_GET['id'] dans la requête SQL : c'est une injection SQL (A05:2025). Un attaquant peut modifier la requête, par exemple avec 1 OR 1=1, et lire toutes les fiches de frais, ce qui porte atteinte à la confidentialité des données personnelles des visiteurs. Correction : requête préparée, $stmt = $pdo->prepare('SELECT * FROM fiche_frais WHERE id = ? AND id_visiteur = ?'); $stmt->execute([$_GET['id'], $_SESSION['id']]);. La valeur est transmise séparément de la requête et ne peut plus en changer la structure ; la condition sur id_visiteur empêche en plus de lire les fiches des autres. »

Elle localise, nomme, donne un exemple d'exploitation, relie au critère et au contexte, corrige en code et justifie. Elle va même au-delà de la question en repérant l'IDOR : c'est ce qui distingue une très bonne copie.

Les pièges classiques

PiègeCe qu'il faut écrire à la place
« Chiffrer les mots de passe »Les hacher avec un algorithme lent et salé (Argon2id, bcrypt) : un chiffrement est réversible
« Le JWT est crypté »Le JWT est signé : son contenu est lisible par tous, mais non modifiable sans le secret
Croire qu'une requête préparée règle toutElle empêche l'injection, pas l'accès aux données d'un autre : il faut aussi la condition sur le propriétaire
Proposer une validation en JavaScript dans le formulaireLa validation se fait côté serveur ; celle du navigateur n'est qu'un confort
Confondre 401 et 403401 : non authentifié ; 403 : authentifié mais non autorisé
Confondre authentification et autorisationQui es-tu, contre as-tu le droit
Mélanger les éditions du Top 10Écrire l'année : A03:2021 Injection n'est pas A03:2025
Une condition d'agrégat dans le WHEREHAVING COUNT(*) >= 5 après le GROUP BY
BETWEEN '2026-03-14' AND '2026-03-14' sur un DATETIMELe second bord vaut minuit : écrire date_heure >= '2026-03-14' AND date_heure < '2026-03-15'
Répondre hors contexteReprendre les noms du sujet (tables, classes, acteurs) et ses enjeux
« Notifier la CNIL sous 72 h » sans nuanceLa notification est due sauf si la violation n'est pas susceptible d'engendrer un risque ; les personnes sont informées si le risque est élevé

Cas corrigé : sécuriser AppliFrais

Contexte. Le laboratoire GSB veut généraliser AppliFrais à ses 480 visiteurs médicaux. Avant la mise en production, la DSI te confie un audit de sécurité de l'application, développée par un prestataire. Tu disposes des documents suivants.

Document 1 : extrait du schéma de la base gsb_frais.

utilisateur (id, login, email, mdp_hash, role, actif)

fiche_frais (id, #id_visiteur, mois, montant_valide, etat, iban_chiffre)

journal (id, date_heure, login, ip, action, resultat)

La colonne mdp_hash contient aujourd'hui l'empreinte SHA-1 du mot de passe, sans sel.

Document 2 : script de connexion de la version PHP.

php
1  <?php
2  session_start();
3  $login = $_POST['login'];
4  $mdp = sha1($_POST['mdp']);
5  $sql = "SELECT id, role FROM utilisateur WHERE login = '$login' AND mdp_hash = '$mdp'";
6  $res = $pdo->query($sql)->fetch();
7  if ($res) {
8      $_SESSION['id'] = $res['id'];
9      $_SESSION['role'] = $res['role'];
10     header('Location: accueil.php');
11 } else {
12     echo "Mot de passe incorrect pour l'utilisateur " . $login;
13 }

Document 3 : classes métier de l'application Java des comptables.

java
class Droit {
    private String nomTable;      // par exemple "fiche_frais"
    private String operation;     // "select", "insert", "update" ou "delete"
    public String getNomTable() { return nomTable; }
    public String getOperation() { return operation; }
}

class Profil {
    private String libelle;
    private List<Droit> lesDroits;
    public List<Droit> getLesDroits() { return lesDroits; }
}

class Utilisateur {
    private int id;
    private String login;
    private Profil leProfil;
    private boolean actif;
    /** Renvoie true si l'utilisateur peut réaliser l'opération sur la table. */
    public boolean estAutorise(String uneTable, String uneOperation) { /* à écrire */ }
}

Document 4 : extrait de la table journal.

iddate_heureloginipactionresultat
1012026-03-14 07:58:12pbernard10.0.2.25connexionechec
1022026-03-14 07:58:40pbernard10.0.2.25connexionsucces
1032026-03-14 12:31:05sroux10.0.2.44connexionsucces
1042026-03-14 22:47:19cpetit203.0.113.9connexionechec
1052026-03-14 22:47:51cpetit203.0.113.9connexionechec
1062026-03-14 22:48:30cpetit203.0.113.9connexionechec
1072026-03-14 22:49:02cpetit203.0.113.9connexionechec
1082026-03-14 23:05:44sroux203.0.113.9connexionechec
1092026-03-15 08:02:10cpetit10.0.2.40connexionsucces

Document 5 : route de suppression d'une fiche (API PHP).

php
// DELETE /api/fiches/{id}
public function supprimer(int $id): void {
    $stmt = $this->pdo->prepare('DELETE FROM fiche_frais WHERE id = ?');
    $stmt->execute([$id]);
    $this->repondre(204);
}

Les attributs $_SESSION['id'] et $_SESSION['role'] sont disponibles dans l'API, et la méthode $this->repondre(int $code, ?array $donnees = null) envoie la réponse HTTP.

Exercice 1 sur 5 Dossier A : analyse de risques Réussi À revoir

  1. Une fiche de frais contient les montants, les déplacements et l'IBAN chiffré du visiteur. Sur une échelle de 1 (faible) à 4 (critique), propose et justifie un niveau de besoin en confidentialité et en intégrité.
  2. Le prestataire découvre qu'une faille a permis, pendant trois semaines, à tout visiteur connecté de lire les fiches des autres. Quelles sont les obligations du laboratoire au regard du RGPD ?
Voir la réponseMasquer la réponse de l'exercice 1
  1. Confidentialité : 3 (important). Les fiches contiennent des données personnelles (déplacements, donc localisation et habitudes) et des données financières ; l'IBAN est chiffré, ce qui limite l'impact d'une fuite, mais les montants et les déplacements restent exposés. On peut défendre 4 si l'IBAN était en clair. Intégrité : 4 (critique). Une modification frauduleuse d'un montant ou d'un état entraîne directement un paiement indu ; les fiches sont aussi des pièces comptables. Ce qui compte est la justification liée au contexte, plus que le chiffre lui-même.
  2. C'est une violation de données à caractère personnel (atteinte à la confidentialité). Le laboratoire, responsable de traitement, doit : la documenter dans son registre des violations ; la notifier à la CNIL dans les 72 heures après en avoir pris connaissance, sauf si elle n'est pas susceptible d'engendrer un risque pour les personnes (article 33), ce qui est difficile à soutenir ici ; informer les visiteurs concernés si le risque pour eux est élevé (article 34) ; et bien sûr corriger la faille. Le délégué à la protection des données (DPO), s'il existe, pilote ces démarches.

Tu avais trouvé ?

Exercice 2 sur 5 Dossier B : connexion Réussi À revoir

À partir du document 2 :

  1. Identifie trois failles de sécurité, en indiquant la ligne, la catégorie de l'OWASP Top 10 2025 et le risque.
  2. Réécris le script en corrigeant toutes les failles. On suppose que mdp_hash contient désormais des empreintes produites par password_hash.
  3. Le prestataire propose de remplacer SHA-1 par un chiffrement AES « plus moderne ». Que lui réponds-tu ?
  4. Comment migrer les empreintes SHA-1 existantes sans imposer un changement de mot de passe à tous les visiteurs ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. Par exemple :
    • Ligne 5 : la saisie $login est concaténée dans la requête : injection SQL (A05). Avec admin' -- , l'attaquant se connecte sans mot de passe ; il peut aussi lire la base.
    • Ligne 4 : empreinte SHA-1 sans sel (A04) : en cas de vol de la base, les mots de passe sont retrouvés en masse (tables précalculées, force brute rapide).
    • Ligne 12 : le login est affiché sans encodage : XSS réfléchie (A05). Le message révèle aussi que le compte existe (énumération), et la session n'est pas régénérée à la connexion (fixation de session, A07). Aucune limite de tentatives n'est visible (A07).
  2. Une version corrigée :
php
<?php
session_start();
$login = $_POST['login'] ?? '';
$mdp = $_POST['mdp'] ?? '';
$stmt = $pdo->prepare('SELECT id, role, mdp_hash FROM utilisateur WHERE login = :login AND actif = 1');
$stmt->execute(['login' => $login]);
$utilisateur = $stmt->fetch(PDO::FETCH_ASSOC);
if ($utilisateur && password_verify($mdp, $utilisateur['mdp_hash'])) {
    session_regenerate_id(true);
    $_SESSION['id'] = $utilisateur['id'];
    $_SESSION['role'] = $utilisateur['role'];
    journaliser($pdo, $login, 'connexion', 'succes');
    header('Location: accueil.php');
    exit;
}
journaliser($pdo, $login, 'connexion', 'echec');
echo 'Identifiant ou mot de passe incorrect';

La fonction journaliser insère une ligne dans journal avec une requête préparée. On ajoute une limitation des tentatives (par compte et par adresse IP) avant la vérification.

  1. Le chiffrement est réversible : avec la clé, qui doit bien se trouver quelque part sur le serveur, on retrouve tous les mots de passe. Or on n'a jamais besoin de relire un mot de passe, seulement de le vérifier. Il faut un hachage lent et salé conçu pour les mots de passe : password_hash (bcrypt, ou Argon2id avec PASSWORD_ARGON2ID).
  2. À chaque connexion réussie vérifiée avec l'ancienne empreinte (hash_equals(sha1($mdp), $utilisateur['mdp_hash'])), on recalcule l'empreinte avec password_hash($mdp, PASSWORD_DEFAULT) et on l'enregistre. Au bout d'une période fixée, les comptes encore en SHA-1 sont désactivés et leurs titulaires réinitialisent leur mot de passe. On peut aussi, dès le départ, envelopper les empreintes existantes : stocker password_hash(sha1_existant), pour ne plus laisser de SHA-1 nu en base.

Tu avais trouvé ?

Exercice 3 sur 5 Dossier C : droits et journaux Réussi À revoir

  1. Écris la méthode estAutorise de la classe Utilisateur (document 3). Un utilisateur inactif n'a aucun droit.
  2. Propose trois cas de test unitaire pour cette méthode, avec le résultat attendu, pour un utilisateur de profil « Comptable » qui a les droits select et update sur fiche_frais.
  3. Écris la requête qui affiche, pour la journée du 14 mars 2026, les logins ayant subi au moins 3 échecs de connexion, avec leur nombre d'échecs. Donne son résultat sur le document 4.
  4. Un collègue a écrit la condition de date WHERE date_heure BETWEEN '2026-03-14' AND '2026-03-14'. Que renvoie sa requête, et pourquoi ?
  5. Analyse les lignes 104 à 109 : que s'est-il probablement passé, et que recommandes-tu ?
Voir la réponseMasquer la réponse de l'exercice 3
  1. On parcourt les droits du profil et on renvoie true dès qu'un droit correspond à la table et à l'opération :
java
public boolean estAutorise(String uneTable, String uneOperation) {
    if (!this.actif || this.leProfil == null) {
        return false;
    }
    for (Droit unDroit : this.leProfil.getLesDroits()) {
        if (unDroit.getNomTable().equals(uneTable) && unDroit.getOperation().equals(uneOperation)) {
            return true;
        }
    }
    return false;
}

equals compare le contenu des chaînes (== comparerait les références). Le return false final applique le refus par défaut.

  1. Par exemple :
    • estAutorise("fiche_frais", "update") renvoie true : droit présent ;
    • estAutorise("fiche_frais", "delete") renvoie false : table connue, opération non accordée ;
    • estAutorise("utilisateur", "select") renvoie false : table absente des droits ;
    • (quatrième cas utile) un utilisateur du même profil mais inactif : estAutorise("fiche_frais", "select") renvoie false.
  2. La requête :
sql
SELECT login, COUNT(*) AS nb_echecs
FROM journal
WHERE action = 'connexion'
  AND resultat = 'echec'
  AND date_heure >= '2026-03-14 00:00:00'
  AND date_heure < '2026-03-15 00:00:00'
GROUP BY login
HAVING COUNT(*) >= 3;
loginnb_echecs
cpetit4

pbernard et sroux n'ont qu'un échec chacun ce jour-là.

  1. Elle ne renvoie aucune ligne : comparée à un DATETIME, la chaîne '2026-03-14' vaut 2026-03-14 00:00:00. La condition ne retient que les événements survenus exactement à minuit. Il faut un intervalle semi-ouvert (>= '2026-03-14' et < '2026-03-15'), ou DATE(date_heure) = '2026-03-14' (plus lisible, mais qui empêche l'utilisation d'un index sur date_heure).
  2. Depuis une adresse externe (203.0.113.9), le soir, quatre échecs en moins de deux minutes sur cpetit, puis un essai sur sroux : une tentative de force brute suivie d'un essai sur un autre compte, sans succès. La connexion réussie de cpetit le lendemain matin vient du réseau interne : c'est sans doute la vraie titulaire. Recommandations : limiter les tentatives par compte et par adresse IP, alerter sur ce type de séquence, bloquer temporairement l'adresse, prévenir les titulaires, envisager la MFA, et vérifier qu'aucune autre adresse n'a obtenu de succès sur ces comptes.

Tu avais trouvé ?

Exercice 4 sur 5 Dossier D : API Réussi À revoir

  1. Explique pourquoi la route du document 5 est dangereuse.
  2. Réécris-la : seul un comptable peut supprimer une fiche, et seulement si elle est à l'état CR ; la suppression est journalisée. Utilise des codes HTTP adaptés.
  3. Le directeur commercial voudrait publier sur le site du laboratoire des statistiques de frais par région, alimentées par l'API. Pourquoi est-il préférable de créer une API publique distincte, en lecture seule, plutôt que d'ouvrir une route de l'API existante ?
Voir la réponseMasquer la réponse de l'exercice 4
  1. Elle n'effectue aucun contrôle : ni authentification, ni rôle, ni état de la fiche. N'importe qui peut supprimer n'importe quelle fiche, y compris une fiche validée ou remboursée (atteinte à l'intégrité et à la disponibilité des données, et à la preuve comptable). La méthode DELETE est irréversible. Rien n'est journalisé.
  2. Une version corrigée :
php
public function supprimer(int $id): void {
    if (!isset($_SESSION['id'])) {
        $this->repondre(401);
        return;
    }
    if ($_SESSION['role'] !== 'comptable') {
        $this->repondre(403);
        return;
    }
    $stmt = $this->pdo->prepare("DELETE FROM fiche_frais WHERE id = ? AND etat = 'CR'");
    $stmt->execute([$id]);
    if ($stmt->rowCount() === 0) {
        $this->repondre(404, ['erreur' => 'Fiche introuvable ou non supprimable']);
        return;
    }
    $this->journaliser($_SESSION['id'], 'suppression_fiche', "fiche:$id");
    $this->repondre(204);
}

Le return après chaque refus empêche d'exécuter la suite. On peut renvoyer 409 plutôt que 404 quand la fiche existe mais n'est plus à l'état CR, au prix d'une requête de plus.

  1. Une API distincte en lecture seule applique la réduction de la surface d'attaque et le moindre privilège : elle n'expose que des routes GET de statistiques agrégées, sans données personnelles, avec un compte de base qui n'a que des droits de lecture (au mieux sur une vue d'agrégats). Une faille dans cette API ne permet ni de modifier des données, ni d'atteindre les fiches individuelles. Ouvrir une route de l'API existante obligerait à gérer une exception « publique » au milieu de routes authentifiées, source d'erreurs, et exposerait sur Internet un code qui donne accès à toutes les données.

Tu avais trouvé ?

Exercice 5 sur 5 Dossier E : affichage des libellés Réussi À revoir

Un testeur a enregistré le libellé <img src=x onerror=alert(document.cookie)> dans une ligne de frais. Quand le comptable ouvre la fiche, une fenêtre affiche son cookie de session.

  1. Nomme la faille et son type.
  2. Propose trois mesures complémentaires, en précisant laquelle corrige la cause.
Voir la réponseMasquer la réponse de l'exercice 5
  1. Une XSS stockée (cross-site scripting, A05:2025) : le libellé enregistré en base est affiché sans encodage dans la page du comptable, et le script s'exécute dans son navigateur.
  2. L'encodage des sorties corrige la cause : htmlspecialchars($ligne['libelle'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') à l'affichage (ou un moteur de gabarits qui encode par défaut). L'attribut HttpOnly sur le cookie de session empêche le script de le lire. Une CSP stricte (script-src 'self', sans 'unsafe-inline') bloque l'exécution de scripts injectés. On peut ajouter une validation du libellé à l'entrée (longueur, caractères attendus), qui réduit la surface d'attaque sans remplacer l'encodage.

Tu avais trouvé ?

La checklist de la veille de l'épreuve

Je sais…Chapitre
citer les critères DICP et justifier un niveau de besoin dans un contexte1
nommer les dix catégories du Top 10 2025 et classer une faille2
corriger une IDOR en ajoutant le propriétaire dans la requête3
écrire une méthode qui vérifie un droit dans une liste (refus par défaut)3
expliquer pourquoi un secret n'a rien à faire dans le code4
expliquer hachage contre chiffrement, et pourquoi Argon2id ou bcrypt6
écrire une requête préparée en PHP, en Java ou en JavaScript7
encoder une sortie et expliquer les trois types de XSS8
expliquer une CSRF et ses parades9
décrire un processus de réinitialisation de mot de passe sûr11
expliquer ce que contient et ce que garantit un JWT11
écrire une requête d'analyse de journal avec GROUP BY et HAVING12
donner les champs d'un journal conforme aux recommandations de la CNIL et sa durée de conservation12
choisir le bon code HTTP (401, 403, 404, 409)13
expliquer les précautions de la biométrie sur mobile13

Le jour de l'épreuve : lis tout le sujet avant de commencer, repère les documents utiles à chaque question, commence par les questions que tu maîtrises, garde du temps pour te relire, et reprends toujours le vocabulaire et les noms du contexte.

Pour aller plus loin. Le cours Épreuve E7 SLAM prépare l'écrit dans son ensemble : lire un sujet et gérer les 4 heures, rédiger une réponse et deux épreuves blanches corrigées.

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Cours suivant : CI/CDRevoir le sommaire du cours