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 questions | Exemples tirés des sujets récents | Chapitre |
|---|---|---|
| Analyse de risques | Fixer et justifier le niveau de besoin d'un critère DICP ; impacts d'une attaque ; mesures contre un rançongiciel | 1 ; sauvegardes contre le rançongiciel : cours Sauvegarde et PRA |
| Protection des mots de passe et des données | Pourquoi ne pas stocker un mot de passe en clair ; hachage ou chiffrement ; chiffrer une colonne sensible | 6 |
| 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éthode | 3 |
| Droits en base de données | Créer un compte, accorder les droits d'une matrice, créer une vue qui restreint l'accès | cours SQL |
| Journalisation | Ajouter 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 journaux | 12 |
| Triggers d'audit | Trouver pourquoi un trigger d'historique ne fonctionne pas, le corriger | cours SQL et procédures et triggers |
| API REST | Dangers 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 seule | 13 |
| Authentification | Mot de passe oublié : failles du processus et processus sécurisé ; SSO et jetons ; comparer un code PIN et un mot de passe | 11 |
| Application mobile | Biométrie et RGPD ; journal des tentatives dans une base SQLite | 13 |
| Droit | Accès frauduleux (Code pénal), RGPD (violation de données, données sensibles, durée de conservation), charte informatique | 1 |
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 :
- 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 ». - 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 ».
- 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.
- 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 avec1 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 surid_visiteurempê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ège | Ce 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 tout | Elle 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 formulaire | La validation se fait côté serveur ; celle du navigateur n'est qu'un confort |
| Confondre 401 et 403 | 401 : non authentifié ; 403 : authentifié mais non autorisé |
| Confondre authentification et autorisation | Qui 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 WHERE | HAVING COUNT(*) >= 5 après le GROUP BY |
BETWEEN '2026-03-14' AND '2026-03-14' sur un DATETIME | Le second bord vaut minuit : écrire date_heure >= '2026-03-14' AND date_heure < '2026-03-15' |
| Répondre hors contexte | Reprendre les noms du sujet (tables, classes, acteurs) et ses enjeux |
| « Notifier la CNIL sous 72 h » sans nuance | La 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.
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.
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.
| id | date_heure | login | ip | action | resultat |
|---|---|---|---|---|---|
| 101 | 2026-03-14 07:58:12 | pbernard | 10.0.2.25 | connexion | echec |
| 102 | 2026-03-14 07:58:40 | pbernard | 10.0.2.25 | connexion | succes |
| 103 | 2026-03-14 12:31:05 | sroux | 10.0.2.44 | connexion | succes |
| 104 | 2026-03-14 22:47:19 | cpetit | 203.0.113.9 | connexion | echec |
| 105 | 2026-03-14 22:47:51 | cpetit | 203.0.113.9 | connexion | echec |
| 106 | 2026-03-14 22:48:30 | cpetit | 203.0.113.9 | connexion | echec |
| 107 | 2026-03-14 22:49:02 | cpetit | 203.0.113.9 | connexion | echec |
| 108 | 2026-03-14 23:05:44 | sroux | 203.0.113.9 | connexion | echec |
| 109 | 2026-03-15 08:02:10 | cpetit | 10.0.2.40 | connexion | succes |
Document 5 : route de suppression d'une fiche (API 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
- 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é.
- 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
- 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.
- 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 :
- Identifie trois failles de sécurité, en indiquant la ligne, la catégorie de l'OWASP Top 10 2025 et le risque.
- Réécris le script en corrigeant toutes les failles. On suppose que
mdp_hashcontient désormais des empreintes produites parpassword_hash. - Le prestataire propose de remplacer SHA-1 par un chiffrement AES « plus moderne ». Que lui réponds-tu ?
- 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
- Par exemple :
- Ligne 5 : la saisie
$loginest concaténée dans la requête : injection SQL (A05). Avecadmin' --, 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).
- Ligne 5 : la saisie
- Une version corrigée :
<?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.
- 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 avecPASSWORD_ARGON2ID). - À chaque connexion réussie vérifiée avec l'ancienne empreinte (
hash_equals(sha1($mdp), $utilisateur['mdp_hash'])), on recalcule l'empreinte avecpassword_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 : stockerpassword_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
- Écris la méthode
estAutorisede la classeUtilisateur(document 3). Un utilisateur inactif n'a aucun droit. - 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
selectetupdatesurfiche_frais. - É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.
- 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 ? - 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
- On parcourt les droits du profil et on renvoie
truedès qu'un droit correspond à la table et à l'opération :
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.
- Par exemple :
estAutorise("fiche_frais", "update")renvoietrue: droit présent ;estAutorise("fiche_frais", "delete")renvoiefalse: table connue, opération non accordée ;estAutorise("utilisateur", "select")renvoiefalse: table absente des droits ;- (quatrième cas utile) un utilisateur du même profil mais inactif :
estAutorise("fiche_frais", "select")renvoiefalse.
- La requête :
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;
| login | nb_echecs |
|---|---|
| cpetit | 4 |
pbernard et sroux n'ont qu'un échec chacun ce jour-là.
- Elle ne renvoie aucune ligne : comparée à un
DATETIME, la chaîne'2026-03-14'vaut2026-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'), ouDATE(date_heure) = '2026-03-14'(plus lisible, mais qui empêche l'utilisation d'un index surdate_heure). - Depuis une adresse externe (203.0.113.9), le soir, quatre échecs en moins de deux minutes sur
cpetit, puis un essai sursroux: une tentative de force brute suivie d'un essai sur un autre compte, sans succès. La connexion réussie decpetitle 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
- Explique pourquoi la route du document 5 est dangereuse.
- 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. - 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
- 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
DELETEest irréversible. Rien n'est journalisé. - Une version corrigée :
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.
- 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
GETde 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.
- Nomme la faille et son type.
- Propose trois mesures complémentaires, en précisant laquelle corrige la cause.
Voir la réponseMasquer la réponse de l'exercice 5
- 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.
- 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'attributHttpOnlysur 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 contexte | 1 |
| nommer les dix catégories du Top 10 2025 et classer une faille | 2 |
| corriger une IDOR en ajoutant le propriétaire dans la requête | 3 |
| é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 code | 4 |
| expliquer hachage contre chiffrement, et pourquoi Argon2id ou bcrypt | 6 |
| écrire une requête préparée en PHP, en Java ou en JavaScript | 7 |
| encoder une sortie et expliquer les trois types de XSS | 8 |
| expliquer une CSRF et ses parades | 9 |
| décrire un processus de réinitialisation de mot de passe sûr | 11 |
| expliquer ce que contient et ce que garantit un JWT | 11 |
écrire une requête d'analyse de journal avec GROUP BY et HAVING | 12 |
| donner les champs d'un journal conforme aux recommandations de la CNIL et sa durée de conservation | 12 |
| choisir le bon code HTTP (401, 403, 404, 409) | 13 |
| expliquer les précautions de la biométrie sur mobile | 13 |
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.