Modéliser la menace, et le faire tôt
Jour 1 · Modéliser la menace sur devsecops-lab · 25 min
Vous avez passé deux semaines à casser et réparer devsecops-lab : SAST, secrets, dépendances à CVE, image durcie, cluster. Vous connaissez ses dix failles par leur numéro. Le module d'aujourd'hui change de posture : au lieu de chercher des bugs un par un, vous allez raisonner sur l'architecture — les composants, les flux, les frontières — pour anticiper ce qui peut mal tourner, avant qu'un outil ne le confirme.
C'est cela, le threat modeling : un raisonnement structuré, mené tôt, sur ce qu'un attaquant pourrait faire de votre système. Il ne remplace ni le SAST ni le pentest — il les oriente. Un scanner lit le code ; un threat model lit les intentions de l'architecture, et voit des choses qu'aucun scanner ne verra jamais : une donnée sensible qui traverse une frontière sans contrôle, une hypothèse de confiance qui n'a jamais été écrite.
Un threat model se rate d'une seule façon : en le traitant comme un formulaire à remplir. Une table STRIDE de trente lignes recopiées d'un modèle générique ne vaut rien. Une table de huit lignes, dont chacune pointe une VULN réelle de votre appli et dit ce qu'on en fait, vaut un audit. Tout ce module est construit pour vous garder du côté du concret : chaque menace que vous écrirez devra se rattacher à une ligne de app.py.
Les quatre questions
Adam Shostack, qui a formalisé la méthode chez Microsoft, la ramène à quatre questions. Elles sont votre fil conducteur des deux jours :
| Question | Ce qu'elle produit | Où, dans ce module |
|---|---|---|
| Qu'est-ce qu'on construit ? | Un schéma des composants et des flux : le DFD | Leçon suivante et TP 1 |
| Qu'est-ce qui peut mal tourner ? | La liste des menaces, cadrée par STRIDE | TP 2 (STRIDE) et TP 3 (attack tree) |
| Qu'est-ce qu'on fait à ce sujet ? | Priorisation (CVSS, DREAD) et traitement | TP 4 et jour 2 (registre de risques) |
| A-t-on fait du bon travail ? | On rejoue une attaque et on vérifie la détection | Jour 2 (simulation d'incident) |
La brique de départ : le diagramme de flux de données
On ne peut pas raisonner sur des menaces sans un schéma d'accord sur ce qu'on protège. Le DFD (Data Flow Diagram) est ce schéma. Il ne connaît que quatre types d'éléments — et c'est sa force : quatre formes suffisent à décrire n'importe quel système.
| Élément | Ce que c'est | Dans devsecops-lab |
|---|---|---|
| Entité externe | Ce qui interagit avec le système sans en faire partie | Le client / navigateur |
| Processus | Ce qui transforme la donnée | L'appli Flask app.py, le sous-processus ping |
| Dépôt de données | Ce qui stocke et restitue, sans transformer | La base SQLite lab.db, les secrets en dur |
| Flux de données | Une donnée qui circule d'un élément à un autre | La requête HTTP, la requête SQL, la commande shell |
Le cinquième trait n'est pas un élément mais une ligne : la frontière de confiance. Elle marque l'endroit où la donnée change de main — où elle passe d'un contexte contrôlé à un contexte qui ne l'est pas, ou l'inverse. C'est là, et presque uniquement là, que vivent les menaces. Un flux qui reste à l'intérieur d'une même zone de confiance est rarement intéressant ; un flux qui traverse une frontière l'est toujours.
La leçon suivante superpose STRIDE à ce DFD : à chaque endroit où un flux franchit une frontière, elle donne un vocabulaire pour nommer ce qui peut mal tourner.
Intention — désamorcer d'emblée le travers du module : le tableau STRIDE rempli par obéissance. Le remède est posé ici : le threat modeling n'est pas un formulaire, c'est quatre questions de Shostack appliquées à une appli qu'Elie connaît ligne à ligne. On installe aussi le DFD comme réponse à la première question — « qu'est-ce qu'on construit ? » — parce que sans schéma partagé, STRIDE n'a rien sur quoi mordre.
Ce qui coince
- Le threat modeling confondu avec le pentest. Signe : « on n'a qu'à scanner, on verra bien ». Or M2 a déjà scanné ; le modèle sert justement à voir ce qu'aucun scanner ne voit — une frontière de confiance mal placée, une hypothèse implicite.
- L'envie de produire un modèle « exhaustif » et donc creux. Un modèle utile est court et priorisé. Le dire avant TP2, sinon la table STRIDE fera trente lignes vides.
Questions à anticiper
- « À quoi bon, puisque j'ai le code sous les yeux ? » — justement : le modèle est ce qui reste quand le code change, et ce qu'on donne à quelqu'un qui n'a pas le code. C'est le livrable que M5 attaque et que le PFR audite.
- « Combien de temps ça prend, un vrai threat model ? » — une demi-journée pour une appli de cette taille ; le nôtre tient sur les deux jours parce qu'on le double d'un registre de risques et d'un exercice d'incident.
Ce qu'il faut exiger — qu'il formule les quatre questions avec ses mots et désigne, sur devsecops-lab, ce qui répond à chacune. S'il ne sait pas dire « ce qu'on construit », le DFD du TP suivant sera décoratif.
STRIDE, posé sur devsecops-lab
Jour 1 · Modéliser la menace sur devsecops-lab · 30 min
STRIDE est une liste de contrôle : six catégories de menaces, une par lettre. Son intérêt n'est pas d'être exhaustive — elle ne l'est pas — mais d'être rapide à poser sur un DFD. Devant chaque élément et chaque flux, vous vous demandez : « et si quelqu'un faisait ça ici ? », six fois, une fois par lettre.
La clé pour ne jamais confondre les catégories : chacune est la négation d'une propriété de sécurité. Retenez la propriété, vous retrouvez la menace.
| Lettre | Menace | Propriété niée | Où, dans devsecops-lab |
|---|---|---|---|
| S | Spoofing — usurper une identité | Authentification | /login : l'injection SQL VULN-01 vous connecte en admin sans mot de passe |
| T | Tampering — altérer une donnée | Intégrité | La même SQLi écrit dans la base ; VULN-02 (XSS) altère la page rendue au client |
| R | Repudiation — nier un acte | Traçabilité | L'appli ne journalise quasiment rien : un acte malveillant ne laisse pas de trace exploitable |
| I | Information disclosure — divulguer | Confidentialité | VULN-04 (IDOR) lit d'autres profils, VULN-08 (SSRF) lit des ressources internes, VULN-09 (debug) crache la pile |
| D | Denial of service — rendre indisponible | Disponibilité | VULN-03 permet une commande coûteuse ; aucune limite de débit sur les routes |
| E | Elevation of privilege — s'élever | Autorisation | VULN-03 et VULN-07 exécutent du code arbitraire ; le conteneur tourne en root (VULN-09) |
L'injection SQL de /login apparaît en S (elle usurpe l'admin), en T (elle écrit en base) et pourrait figurer en E. Ne cherchez pas à la ranger dans une seule case : notez-la là où son effet est le plus grave — ici Spoofing, car le but de l'attaquant est de devenir admin — et mentionnez les autres. STRIDE est une grille de lecture, pas un tiroir à une place.
La menace qu'on oublie toujours : Repudiation
Regardez la colonne R. devsecops-lab n'écrit pratiquement aucun journal applicatif : le seul témoin des requêtes est le journal d'accès de Werkzeug. Conséquence directe : si un attaquant contourne l'authentification, vous ne pourrez pas reconstituer proprement ce qu'il a fait. Cette faiblesse ne casse rien aujourd'hui — c'est pourquoi on l'oublie — mais elle décide de tout au moment de l'incident. Vous la retrouverez lundi, quand vous jouerez une attaque sur votre propre maquette et tenterez d'en reconstruire la chronologie à partir de journaux qui n'existent qu'à moitié.
STRIDE, et les autres méthodes
STRIDE n'est pas seul. PASTA part du risque métier et remonte vers la technique ; on s'en servira demain comme grille de priorisation. LINDDUN vise la vie privée. Attack trees décomposent un objectif d'attaquant — c'est la leçon qui suit. On combine : STRIDE pour énumérer largement, l'attack tree pour approfondir un scénario qui fait peur.
STRIDE se retient par sa symétrie : chaque lettre est la négation d'une propriété de sécurité. Spoofing nie l'authentification, Tampering l'intégrité, Repudiation la traçabilité, Information disclosure la confidentialité, Denial of service la disponibilité, Elevation of privilege l'autorisation. Si vous savez réciter la propriété niée, vous savez à quelle case rattacher une faille.
Le piège classique : ranger l'injection SQL dans « Tampering » parce qu'elle modifie la base. C'est vrai, mais son effet premier sur /login est de vous faire passer pour l'admin — donc Spoofing. Une même faille peut peupler plusieurs cases ; notez-la là où elle fait le plus de dégâts, puis mentionnez les autres.
Intention — donner le vocabulaire STRIDE et, surtout, l'accrocher aux VULN déjà connues plutôt qu'à des exemples abstraits. À la fin de la leçon, Elie doit voir STRIDE non comme six mots mais comme six manières de nier une propriété, chacune illustrée par une ligne de app.py.
Ce qui coince
- Le classement « une faille, une case ». STRIDE n'est pas une partition : SQLi est à la fois Spoofing (contournement d'auth), Tampering (écriture en base) et Elevation. Lui faire admettre le recouvrement, sinon la table de TP2 sera artificiellement pauvre.
- Repudiation systématiquement oubliée, parce que devsecops-lab ne journalise presque rien — et c'est précisément la menace la plus intéressante ici : l'absence de traçabilité sera le nœud de la simulation de J2. Le semer maintenant.
- Elevation of privilege confondu avec Information disclosure. Repère : lire une donnée qu'on ne devrait pas = I ; obtenir un pouvoir qu'on ne devrait pas (exécuter du code, devenir admin) = E.
Questions à anticiper
- « Faut-il une menace par case et par élément ? » — non : STRIDE-per-element est un guide, pas une obligation de remplissage. Une case vide justifiée vaut mieux qu'une case pleine inventée.
- « Pourquoi pas PASTA ou LINDDUN ? » — les citer : PASTA est centré risque métier, LINDDUN vie privée ; STRIDE est le plus rapide à poser sur un DFD, on garde PASTA comme grille de priorisation au J2.
Ce qu'il faut exiger — qu'il rattache, sans la page, au moins quatre des six lettres à une VULN précise, et qu'il cite Repudiation comme la faiblesse structurelle de l'appli (rien n'est journalisé).
Quiz — DFD, STRIDE et attack trees
Jour 1 · Modéliser la menace sur devsecops-lab · 10 min
1. Dans un DFD, qu'est-ce qui distingue une frontière de confiance d'un simple flux de données ?
2. L'injection SQL de /login (VULN-01) permet de se connecter en tant qu'admin sans mot de passe. À quelle catégorie STRIDE la rattacher en priorité ?
3. Pourquoi conduire le threat modeling au niveau de la conception, plutôt qu'une fois le pentest terminé ?
4. Dans le DFD de devsecops-lab, comment classer la base SQLite lab.db ?
5. Dans un attack tree, que signifie un nœud AND par rapport à un nœud OR ?
Intention — vérifier que le vocabulaire est opérationnel avant les trois TP, pas qu'il est récité. q1 teste la frontière de confiance (le concept qui porte tout le DFD), q2 le mapping STRIDE sur une faille réelle, q3 le « pourquoi tôt », q4 la typologie DFD, q5 la sémantique AND/OR des attack trees.
Ce qui coince
- q2 : rattacher la SQLi à Denial of service ou Repudiation. Une erreur ici dit que la propriété niée n'est pas comprise — reprendre le tableau de L2 avant TP2.
- q5 : inverser AND et OR. Bénin sur le principe, mais un attack tree avec les nœuds inversés est faux de bout en bout ; corriger avant TP3.
Questions à anticiper
- « La SQLi, c'est S ou T ? » — les deux ; la question force le premier effet (usurpation). Rappeler que STRIDE se recoupe.
Ce qu'un échec révèle — q1 et q4 justes suffisent pour lancer le DFD ; q2 fausse impose une reprise de cinq minutes sur « propriété niée » avant STRIDE.
TP 1 — Le DFD de devsecops-lab
Jour 1 · Modéliser la menace sur devsecops-lab · 55 min
Dessiner le diagramme de flux de données de votre appli, avec ses frontières de confiance, et le poser dans le rapport de threat model.
Le fichier rapports/threat-model.md. Il grandira jusqu'au point de contrôle : DFD (ce TP), table STRIDE (TP2), attack tree (TP3), findings notés en CVSS (TP4). C'est le livrable que M5 attaquera et que le PFR auditera — écrivez-le pour quelqu'un qui n'a pas votre code sous les yeux.
Le DFD s'écrit en Mermaid : du texte que GitHub rend en diagramme, versionnable, sans outil propriétaire (ni Microsoft Threat Modeling Tool, qui n'existe que sous Windows).
- Placez-vous à la racine de votre dépôt et créez le rapport. Ouvrez-le par le contexte, en une phrase — c'est la réponse à « qu'est-ce qu'on construit ? » :
cd devsecops-lab mkdir -p rapports cat > rapports/threat-model.md <<'EOF' # Threat model - devsecops-lab Perimetre : application Flask locale (app.py, ~130 lignes), port 5000, base SQLite lab.db, deployee sur localhost uniquement. Objet du modele : anticiper les menaces avant exploitation (M5). ## 1. Ce qu'on construit - DFD EOF - Listez d'abord, en clair, les éléments de chaque type. Ne dessinez rien encore : relisez
app.pyroute par route et rangez chaque brique dans l'une des quatre catégories. Entités externes (client, URL tierce du/fetch), processus (Flask, le shell de/ping), dépôts (SQLite, les secrets en dur), flux (HTTP, SQL, commande shell, GET sortant). - Écrivez le DFD en Mermaid, dans le rapport. Les deux sous-graphes matérialisent deux zones de confiance ; l'URL tierce reste dehors. Adaptez les libellés, mais gardez les quatre flux — ce sont eux qui portent les menaces :
```mermaid flowchart LR U[Client / navigateur] subgraph serveur[Zone serveur - localhost:5000] F([Flask app.py]) DB[(SQLite lab.db)] SH([subprocess ping - shell=True]) SEC[/SECRET_KEY et API_KEY en dur/] end EXT[URL tierce - cible SSRF] U -- requete HTTP --> F F -- requete SQL concatenee / VULN-01 --> DB F -- host non filtre / VULN-03 --> SH F -- GET url fournie / VULN-08 --> EXT SEC -. clef lue / VULN-05 .-> F ``` - Sous le diagramme, listez les frontières de confiance et, pour chaque flux qui en franchit une, la phrase « qui fait confiance à qui ». C'est le cœur du livrable :
cat >> rapports/threat-model.md <<'EOF' ### Frontieres de confiance - FC1 reseau : Client -> Flask. Flask fait confiance a une entree HTTP non authentifiee. - FC2 processus : Flask -> shell. Flask fait confiance a une chaine construite depuis le parametre 'host' pour l'executer dans un shell. - FC3 donnees : Flask -> SQLite. Flask fait confiance a une requete concatenee. - FC4 sortant : Flask -> URL tierce. Flask fait confiance a une URL fournie par le client. - FC5 secret : les secrets ne devraient jamais quitter le serveur ; VULN-05 les expose dans la source versionnee. EOF - Vérifiez le rendu. Ouvrez
rapports/threat-model.mddans l'aperçu de votre éditeur ou poussez sur une branche pour voir GitHub rendre le Mermaid. Le diagramme doit s'afficher, pas rester du texte : si c'est du texte, le bloc n'est pas ouvert par```mermaid. - Committez le premier jet, sur une branche de travail (la
mainest protégée) :git checkout -b feat/threat-model git add rapports/threat-model.md git commit -m "threat model: DFD et frontieres de confiance de devsecops-lab"
- Le flux
Flask → shellfranchit une frontière de processus. En une phrase, qui fait confiance à qui, et pourquoi cette confiance est-elle mal placée ? - Les secrets en dur (VULN-05) sont modélisés en dépôt de données interne. Quelle frontière leur exposition viole-t-elle, et à quel moment de la vie du dépôt ?
- Votre DFD ne montre aucune frontière à l'intérieur de la zone serveur entre Flask et SQLite au sens réseau. Est-ce correct, ou manque-t-il une frontière de confiance ?
Un DFD juste tient sur une page. Si le vôtre déborde, c'est que vous décrivez l'implémentation (fonctions, variables) au lieu des flux. Restez aux quatre formes : entité externe, processus, dépôt, flux — et une seule frontière par changement de contexte.
La question qui révèle un bon DFD : « pour chaque flux, qui fait confiance à qui, et pourquoi ? ». Le flux Flask → shell traverse une frontière de processus : Flask fait confiance à une chaîne construite depuis une entrée utilisateur. Écrivez cette phrase à côté de chaque flux qui franchit une frontière — c'est elle, pas le dessin, qui servira à STRIDE au TP suivant.
Intention — produire le premier livrable du module, threat-model.md, et surtout faire nommer les frontières de confiance. Le DFD n'est pas une fin : c'est le support sur lequel STRIDE va mordre au TP2. Un DFD sans frontières explicites rend le TP2 creux.
Ce qui coince
- Le DFD glisse vers le diagramme de classes ou l'organigramme d'appels. Symptôme : il y a des fonctions Python dans les nœuds. Recadrer : un nœud = une entité, un processus ou un dépôt, jamais une fonction.
- Frontière unique « Internet / serveur » et rien d'autre. Or il y en a au moins trois ici : réseau (client↔Flask), processus (Flask↔shell), et l'accès aux secrets. La SSRF n'a de sens que si l'on voit la frontière Flask↔URL tierce.
- Mermaid qui ne rend pas sur GitHub : bloc ouvert par
```mermaid, pas```seul. Vérifiable dans l'aperçu du fichier.
Questions à anticiper
- « Le sous-processus
pingest-il vraiment un élément séparé ? » — oui : il tourne dans un autre contexte d'exécution (le shell), donc une frontière l'en sépare, et c'est toute l'histoire de VULN-03. - « Où mettre les secrets en dur ? » — en dépôt de données interne, lu par Flask ; la frontière est ici conceptuelle (le secret ne devrait jamais quitter le serveur) et c'est ce que VULN-05 viole.
Ce qu'il faut exiger — un DFD rendu (aperçu Mermaid lisible), au moins trois frontières nommées, et pour chaque flux qui en franchit une, la phrase « qui fait confiance à qui ». Sans cette phrase, ne pas valider : le TP2 en dépend.
TP 2 — STRIDE, élément par élément
Jour 1 · Modéliser la menace sur devsecops-lab · 1 h 25
Dérouler STRIDE sur chaque élément et chaque flux du DFD, en reliant chaque menace retenue à une VULN réelle de votre code — ou à un « aucune » assumé.
La méthode : pour chaque flux du DFD (pas chaque ligne de code), passez les six lettres en revue et demandez-vous si la menace est plausible ici. Quand elle l'est, nommez-la et rattachez-la à la VULN qui l'incarne. Quand elle ne l'est pas, écrivez pourquoi. Vous couvrez ainsi les dix VULN sans jamais partir d'une liste abstraite.
- Ouvrez d'abord votre première passe. Vous avez fait un STRIDE de découverte le 5 août, à la fin du module 1 : il est dans
rapports/stride-devsecops-lab-m1.md. Relisez-le maintenant, avant d'écrire quoi que ce soit — puis mettez-le de côté. Vous le confronterez à votre tableau du jour à la dernière étape ; c'est la seule façon de mesurer ce que trois modules d'outillage ont changé à votre regard.cat rapports/stride-devsecops-lab-m1.md - Ajoutez la section STRIDE au rapport, avec l'en-tête du tableau. Une ligne par menace retenue, rattachée à son flux et à sa VULN :
cat >> rapports/threat-model.md <<'EOF' ## 2. Ce qui peut mal tourner - STRIDE | Element / flux | STRIDE | Menace concrete | VULN | Propriete niee | Etat | |---|---|---|---|---|---| EOF - Remplissez le flux
Client → Flask(FC1). C'est le plus riche : presque toutes les entrées passent par là. Exemple de lignes à écrire vous-même dans le tableau :| Client->Flask /login | S | usurper l'admin par SQLi | VULN-01 | Authentification | corrige M1-J3 | | Client->Flask /search| T | injecter du HTML rendu tel quel | VULN-02 | Integrite | corrige M2 | | Client->Flask /profile| I| lire le profil d'un autre (IDOR) | VULN-04 | Confidentialite | ouvert | | Client->Flask (tous) | R| aucun journal applicatif exploitable | - | Tracabilite | ouvert | - Déroulez maintenant les autres flux du DFD, un par un, et couvrez le reste des VULN. Chaque flux du TP1 doit produire au moins une ligne :
Flask → shell(E/D, VULN-03), le flux/configvers le parseur YAML (E, VULN-07, déséralisation),Flask → URL tierce(I, VULN-08, SSRF),Flask → SQLite(T, VULN-01),secrets → Flask(I, VULN-05), et le hachage md5 de/hash(I, VULN-06). - Traitez explicitement les cases vides. Ajoutez sous le tableau deux ou trois décisions de non-menace, argumentées — c'est ce qui prouve que vous avez réfléchi plutôt que rempli :
cat >> rapports/threat-model.md <<'EOF' ### Cases laissees vides, et pourquoi - Spoofing sur Flask->SQLite : ecarte, la base ne porte pas d'identite a usurper ; la menace est en amont, sur la construction de la requete. - Denial of service sur secrets->Flask : ecarte, ce flux est interne et n'expose pas de surface reseau. EOF - Relisez avec l'oeil de l'attaquant de M5 : chaque VULN-01 à VULN-10 doit apparaître au moins une fois dans la table ou dans les cases vides argumentées. Cochez-les sur la table des dix vulnérabilités. S'il en manque une, c'est un flux oublié au DFD — retournez au TP1.
- Reprenez maintenant votre passe du 5 août et comparez-la ligne à ligne avec celle du jour. Écrivez trois lignes de plus au rapport : ce que vous aviez vu et qui tient, ce que vous aviez manqué, et ce que vous aviez surestimé. La question utile n'est pas « ai-je progressé » mais « qu'est-ce qui, dans ce que j'ai appris depuis, m'a fait voir cette ligne-là ».
cat >> rapports/threat-model.md <<'EOF' ### Ecart avec la premiere passe (stride-devsecops-lab-m1.md, 5 aout) - Tenu : - Manque a l'epoque : - Surestime a l'epoque : EOF - Committez :
git add rapports/threat-model.md git commit -m "threat model: table STRIDE reliee aux VULN-01..10"
- Vous avez laissé au moins une case STRIDE vide en la justifiant. Pourquoi une case vide argumentée a-t-elle plus de valeur, pour un auditeur, qu'une case remplie par prudence ?
- Repudiation ne correspond à aucune VULN figée mais figure dans votre table. En quoi est-ce malgré tout la menace la plus importante à documenter avant la simulation d'incident de lundi ?
- VULN-09 (debug=True, en-têtes manquants) relève à la fois de Information disclosure et d'Elevation. Où l'avez-vous rangée, et pourquoi ce choix plutôt que l'autre ?
La tentation est de remplir les six cases pour chaque élément. Résistez : une case vide justifiée (« pas de menace de spoofing sur ce flux car il ne porte aucune identité ») vaut mieux qu'une menace inventée. Un bon tableau STRIDE a des trous, et chaque trou est un choix, pas un oubli.
Test d'auto-vérification : chacune de vos lignes doit pointer soit une VULN-NN, soit une décision explicite. Si une ligne ne cite ni l'une ni l'autre, vous avez recopié un modèle générique — supprimez-la.
Intention — c'est le cœur du jour 1 et l'endroit exact où se joue « tableau rempli sans conviction ou vrai modèle ». On applique STRIDE non pas en l'air mais sur chaque flux du DFD de TP1, et chaque menace retenue doit s'accrocher à une VULN connue.
Ce qui coince
- La complétude pour la complétude : trente lignes, dont vingt vides de sens. Exiger l'inverse — la justification des cases laissées vides — est ce qui transforme l'exercice en raisonnement.
- Les VULN oubliées de la table alors qu'elles ont un flux évident : VULN-08 (SSRF) sur FC4, VULN-07 (désérialisation) sur le flux
/config. Si un flux du DFD n'a aucune menace, c'est suspect. - Repudiation encore vide. La forcer : « si l'attaque de VULN-01 réussit, avec quoi prouves-tu ce qui s'est passé ? » — la réponse (rien) est une menace R à écrire.
Questions à anticiper
- « Dois-je traiter les six lettres pour SQLite ? » — non ; SQLite subit surtout T et I via Flask. Lui faire dire que la menace est sur le flux, pas sur le dépôt.
- « Une menace sans VULN correspondante, je la garde ? » — oui si elle est réelle (ex. absence de rate-limiting = D), en la marquant « pas de VULN figée, risque résiduel ».
Ce qu'il faut exiger — une table où chaque ligne cite une VULN-NN ou une décision, où Repudiation n'est pas vide, et où au moins une case porte un « aucune » argumenté. C'est le critère qui sépare le formulaire du modèle.
La reprise du 5 août — le TP s'ouvre et se referme sur rapports/stride-devsecops-lab-m1.md, sa passe de découverte du module 1. Deux raisons : il ne repart pas d'une page blanche sur un exercice qu'il a déjà fait, et la section « écart » du rapport donne une mesure de progression que ni un quiz ni une démonstration ne donnent. Attention au fichier : le livrable du jour est rapports/threat-model.md, celui de M1 ne doit pas être écrasé.
Attack trees : décomposer un objectif
Jour 1 · Modéliser la menace sur devsecops-lab · 20 min
STRIDE énumère largement. L'attack tree fait l'inverse : il part d'un objectif d'attaquant et le décompose en sous-objectifs, jusqu'à des actions concrètes. Là où STRIDE demande « qu'est-ce qui peut mal tourner partout ? », l'arbre demande « de combien de façons peut-on atteindre ce but précis ? ».
La structure
- La racine est l'objectif de l'attaquant. Elle doit être mesurable : « exécuter une commande arbitraire sur le serveur », pas « pirater l'appli ».
- Les nœuds intermédiaires sont des sous-objectifs.
- Un nœud OR est atteint dès qu'un de ses enfants l'est. Un nœud AND exige que tous ses enfants le soient.
- Les feuilles sont des actions concrètes — chez vous, des VULN avec leur charge d'exploitation.
La leçon de l'arbre est visuelle : sous une racine reliée par un OR, il suffit d'un seul chemin pour gagner. Corriger une branche ne protège de rien tant qu'une autre reste ouverte. C'est exactement le cas de devsecops-lab, où plusieurs failles indépendantes mènent au même résultat — l'exécution de code.
On ne modélise pas tous les objectifs possibles : on choisit celui qui ferait le plus de dégâts. Pour devsecops-lab, c'est « exécuter du code arbitraire sur le serveur » — car de là, tout le reste tombe. Les feuilles seront VULN-01, VULN-03, VULN-05 et VULN-07 : quatre chemins distincts vers le même sommet, ce qui se lit d'un coup d'oeil sur l'arbre et jamais sur une table STRIDE.
Chaque feuille peut porter une étiquette : coût, probabilité, compétence requise. On reste qualitatif aujourd'hui. Demain, CVSS et DREAD donneront des chiffres à ces feuilles, et l'arbre deviendra un outil de priorisation.
Intention — donner l'outil qui complète STRIDE : là où STRIDE énumère largement, l'attack tree approfondit un scénario. On ne construit pas dix arbres ; on en construit un, sur l'objectif qui fait le plus peur, et on montre que ses feuilles sont les VULN déjà cataloguées.
Ce qui coince
- Confusion arbre / DFD. Le DFD décrit le système ; l'arbre décrit une intention d'attaquant. Le premier est statique, le second téléologique.
- Racine trop vague (« pirater l'appli »). Une bonne racine est un objectif mesurable : « exécuter une commande arbitraire sur le serveur ».
- AND/OR inversés — déjà vu au quiz ; rappeler qu'un OR multiplie les chemins, un AND impose une conjonction plus rare donc plus difficile.
Questions à anticiper
- « À quoi ça sert de plus que STRIDE ? » — à raisonner en chemins : l'arbre montre qu'il suffit d'une feuille (OR) pour atteindre la racine, donc que corriger une seule branche ne suffit pas.
- « Peut-on chiffrer les branches ? » — oui : coût, probabilité, compétence requise. On reste qualitatif ici, la quantification arrive au J2 avec CVSS/DREAD.
Ce qu'il faut exiger — qu'il choisisse une racine mesurable et que les feuilles de son arbre soient des VULN concrètes avec une charge d'exploitation, pas des généralités.
TP 3 — Un attack tree vers l'exécution de code
Jour 1 · Modéliser la menace sur devsecops-lab · 55 min
Construire l'arbre d'attaque de l'objectif le plus grave, avec des feuilles qui sont des exploits réels de vos VULN.
Les charges que vous écrivez ici visent uniquement devsecops-lab et Juice Shop, sur votre poste. Ce sont des exploits réels ; comme posé en M1, ils ne s'emploient jamais ailleurs. L'arbre est un document d'analyse, pas une invitation à sortir du localhost.
- Ajoutez la section attack tree au rapport et posez la racine. Choisissez un objectif mesurable :
cat >> rapports/threat-model.md <<'EOF' ## 3. Un scenario approfondi - attack tree Objectif racine : executer une commande arbitraire sur le serveur devsecops-lab. EOF - Écrivez l'arbre en Mermaid. Notez le nœud
OUà la racine (quatre chemins indépendants) et le nœudETsous « détourner la confiance » (voler le secret et savoir signer un cookie) :```mermaid flowchart TD G[But: executer une commande arbitraire sur le serveur] G --> OU{OU} OU --> C[Injecter une commande shell] OU --> D[Injecter un objet deserialise] OU --> B[Contourner l'authentification puis fonctions admin] OU --> T[Detourner la confiance de session] C --> C1[VULN-03: /ping?host=127.0.0.1;id] D --> D1[VULN-07: POST /config payload !!python/object/apply:os.system] B --> B1[VULN-01: user=admin'-- and pwd=x] T --> ET{ET} ET --> T1[VULN-05: lire SECRET_KEY dans la source] ET --> T2[forger un cookie de session signe avec cette clef] ``` - Sous l'arbre, expliquez en deux lignes pourquoi la racine est un
OU: quatre chemins indépendants y mènent, donc corriger une seule branche ne ferme pas l'objectif. C'est la conclusion opérationnelle de l'arbre.cat >> rapports/threat-model.md <<'EOF' Lecture : la racine est un OU. Fermer VULN-03 seule laisse VULN-07, VULN-01 et le detournement de session ouverts. Il faut traiter les quatre branches, pas une. La branche 'session' est un ET : voler la clef ne suffit pas sans savoir signer. EOF - Vérifiez que chaque feuille porte une charge exécutable, pas une catégorie. Relisez :
/ping?host=127.0.0.1;id, la charge YAML,user=admin'--. Si une feuille ne se termine pas par quelque chose que vous pourriez taper, décomposez-la encore. - Committez :
git add rapports/threat-model.md git commit -m "threat model: attack tree vers l'execution de code"
- La racine de votre arbre est reliée par un OU. Quelle conséquence concrète cela a-t-il sur une stratégie de correction qui traiterait les failles « une par une, par ordre de sévérité » ?
- La branche « détourner la confiance de session » est un ET. Que se passerait-il si vous corrigiez VULN-05 (secret en dur) mais laissiez le mécanisme de signature inchangé ?
- STRIDE et l'attack tree décrivent en partie les mêmes failles. Qu'apporte l'arbre que la table STRIDE ne montrait pas ?
Une feuille d'attack tree n'est pas « injection SQL » : c'est user=admin'--&pwd=x. Descendez jusqu'à la charge que vous taperiez vraiment. Une feuille qu'on ne peut pas exécuter n'est pas une feuille, c'est un sous-objectif à décomposer davantage.
Gardez les charges de ce TP sous la main : ce sont exactement celles que vous rejouerez lundi dans la simulation d'incident, et que M5 portera sur Juice Shop.
Intention — matérialiser qu'un seul objectif (exécuter du code) se sert de quatre VULN indépendantes, donc qu'un OR à la racine rend la correction partielle illusoire. L'arbre prépare aussi les charges d'exploitation de la simulation de J2.
Ce qui coince
- Feuilles restées au niveau « catégorie » (« déséralisation ») au lieu de la charge (
!!python/object/apply:os.system). Sans charge, la feuille n'est pas vérifiable et la simulation de lundi n'aura rien à rejouer. - Racine mal choisie : « voler des données » est trop étroit ici. « Exécuter du code » est le sommet dont tout le reste découle.
- Confusion OR/AND sur la branche « forger un cookie » : voler le secret (VULN-05) et savoir signer un cookie sont un AND — les deux sont nécessaires. Bon endroit pour ancrer la sémantique.
Questions à anticiper
- « VULN-01 mène-t-elle vraiment à l'exécution de code ? » — pas directement en SQLite, mais au contournement d'auth qui ouvre les fonctions admin ; nuancer et le noter comme chemin indirect, honnêtement.
- « Faut-il tester les charges maintenant ? » — non, lundi ; aujourd'hui on les écrit et on vérifie qu'elles sont plausibles.
Ce qu'il faut exiger — un arbre à racine mesurable, au moins un nœud AND correctement justifié, et des feuilles portant des charges concrètes réutilisables lundi.
Noter une faille : CVSS v3.1
Jour 1 · Modéliser la menace sur devsecops-lab · 25 min
« Cette faille est grave » n'est pas une note. CVSS v3.1 (Common Vulnerability
Scoring System) transforme une appréciation en un nombre de 0 à 10, calculé à partir de huit
métriques. Son intérêt n'est pas le chiffre seul mais le vecteur qui l'explique :
une chaîne comme CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H dit pourquoi
le score vaut 9.8, et permet à un tiers de refaire le calcul.
Le référentiel de ce parcours l'impose : chaque finding de votre rapport de pentest (M5) et de votre audit (PFR) portera son vecteur complet, pas seulement sa bande. On apprend donc à le construire ici, sur vos propres failles.
Les huit métriques du Base score
| Métrique | Valeurs | Question posée |
|---|---|---|
| AV — Attack Vector | N / A / L / P | D'où attaque-t-on ? Réseau (N), adjacent (A), local (L), physique (P) |
| AC — Attack Complexity | L / H | L'attaque dépend-elle de conditions hors du contrôle de l'attaquant ? |
| PR — Privileges Required | N / L / H | Quels privilèges l'attaquant doit-il déjà posséder ? |
| UI — User Interaction | N / R | La victime doit-elle faire quelque chose (cliquer, ouvrir) ? |
| S — Scope | U / C | L'impact déborde-t-il du composant vulnérable (Changed) ou non (Unchanged) ? |
| C / I / A | N / L / H | Impact sur Confidentialité, Intégrité, Disponibilité |
Les bandes de sévérité — CVSS v3.1
| Bande | Score | Délai de correction attendu |
|---|---|---|
| Critical | 9.0 – 10.0 | immédiat |
| High | 7.0 – 8.9 | ≤ 7 jours |
| Medium | 4.0 – 6.9 | ≤ 30 jours |
| Low | 0.1 – 3.9 | planifié |
| Info | 0.0 | à discrétion |
Le cas de référence : l'injection SQL de /login
Déroulons VULN-01, métrique par métrique. C'est le vecteur donné en modèle par le référentiel, et celui que vous justifierez au TP suivant :
- AV:N — exploitable par une simple requête HTTP à distance.
- AC:L — aucune condition particulière ; la charge marche à tous les coups.
- PR:N — aucun privilège requis : la route est publique.
- UI:N — aucune action de la victime n'est nécessaire.
- S:U — l'impact reste dans le composant attaqué (l'appli et sa base).
- C:H / I:H / A:H — lecture, modification et destruction de la base sont toutes possibles.
Résultat : CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 9.8,
Critical.
L'XSS réfléchi de /search (VULN-02) est aussi une injection. Pourtant son score tombe à 6.1, Medium. Deux différences l'expliquent : elle exige une action de la victime (UI:R — il faut qu'elle ouvre un lien piégé) et son Scope est Changed (S:C — le code s'exécute dans le navigateur, hors du serveur), mais elle n'atteint que partiellement la confidentialité et l'intégrité (C:L / I:L) et pas la disponibilité. Vous calculerez ce 6.1 vous-même au TP — c'est la meilleure preuve que CVSS discrimine, et que « tout n'est pas critique ».
On n'utilise que le Base score : c'est le seul comparable d'une organisation à l'autre. Les scores Temporal (maturité de l'exploit) et Environmental (votre contexte) existent, mais ne se partagent pas — on les laisse de côté.
Vous connaissez déjà ce barème : vous l'avez lu le 7 août, en M2, pour trier les CVE de vos dépendances — le score venait alors de l'advisory, tout fait. Aujourd'hui vous le calculez, sur vos propres failles, qu'aucun advisory ne notera jamais à votre place. C'est le même vecteur et ce n'est pas le même geste.
Le réflexe faux : « c'est grave, donc 9 ». CVSS ne se devine pas, il se calcule à partir de huit métriques. Deux failles « graves » peuvent tomber à 9.8 et à 6.1 selon qu'elles exigent, ou non, une interaction de la victime et selon ce qu'elles atteignent en confidentialité, intégrité, disponibilité.
La métrique qui piège le plus souvent : Scope (S). S:C (Changed) veut dire que la faille déborde du composant vulnérable vers un autre — c'est le cas de l'XSS, qui s'exécute dans le navigateur de la victime, pas dans le serveur. Si vous hésitez entre S:U et S:C, demandez-vous : « l'impact sort-il du composant que j'attaque ? ».
Intention — remplacer l'intuition (« ça a l'air critique ») par un calcul défendable. L'enjeu du parcours : le §8 du référentiel exige un vecteur CVSS complet sur chaque finding de M5 et du PFR. C'est ici qu'Elie apprend à le produire, sur ses propres failles. Attention à la reprise : M2 (7 août) lui a appris à lire un vecteur fourni par un advisory ; ici il l'écrit. S'il dit « on a déjà vu CVSS », lui demander le vecteur de son IDOR — aucun advisory ne le lui donnera.
Ce qui coince
- Score choisi à la louche puis vecteur bricolé pour coller. Inverser : le vecteur d'abord, le score en découle.
- Scope mal compris. C'est la métrique la plus subtile de CVSS 3.1 ; l'XSS (S:C) contre la SQLi (S:U) est l'exemple qui l'éclaire — le garder pour le TP.
- PR (Privileges Required) confondu avec UI. PR = ce que l'attaquant doit posséder ; UI = ce que la victime doit faire.
Questions à anticiper
- « Pourquoi pas juste Critical/High/Low à l'oeil ? » — parce qu'un tiers doit pouvoir recalculer ; le vecteur est la trace de la décision.
- « Base, Temporal, Environmental ? » — on s'en tient au Base score (§8), le seul comparable d'une organisation à l'autre.
Ce qu'il faut exiger — qu'il explique chaque lettre du vecteur de VULN-01 avec ses mots, et qu'il justifie S:U ici / S:C pour l'XSS au TP suivant.
TP 4 — Noter vos failles en CVSS
Jour 1 · Modéliser la menace sur devsecops-lab · 55 min
Calculer, à la main puis avec le calculateur officiel, le vecteur et le score CVSS de trois de vos failles, et vérifier que la note reflète bien l'écart de gravité.
Le calculateur officiel est celui de FIRST (l'organisation qui maintient CVSS) : first.org/cvss/calculator/3.1. Il fonctionne hors-ligne une fois la page chargée et ne demande aucun compte. Vous cliquez les huit métriques, il affiche le score et le vecteur — mais c'est vous qui décidez des métriques.
- Ajoutez la section « findings notés » au rapport :
cat >> rapports/threat-model.md <<'EOF' ## 4. Ce qu'on en fait - priorisation CVSS | VULN | Vecteur CVSS v3.1 | Score | Bande | |---|---|---|---| EOF - Notez d'abord VULN-01 (SQLi) sans regarder le modèle : posez les huit métriques une par une en vous justifiant à voix haute, puis lisez le score. Vous devez retomber sur le vecteur de référence :
| VULN-01 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | 9.8 | Critical | - Notez maintenant VULN-02 (XSS réfléchi). Attention aux deux métriques qui la distinguent : UI:R (la victime doit ouvrir un lien piégé) et S:C (le code s'exécute dans son navigateur, hors du serveur). Impacts partiels : C:L / I:L / A:N. Vérifiez que vous obtenez bien 6.1 :
| VULN-02 | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N | 6.1 | Medium | - Notez enfin VULN-03 (injection de commande). Raisonnez seul, puis comparez : elle s'attaque par le réseau, sans privilège ni interaction, et donne l'exécution de code — donc un impact total. Elle doit retomber au niveau de la SQLi :
| VULN-03 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | 9.8 | Critical | - Écrivez, sous le tableau, une phrase qui explique l'écart entre l'XSS et les deux autres. C'est la trace que vous avez compris, pas seulement calculé :
cat >> rapports/threat-model.md <<'EOF' L'XSS (6.1) est plus basse que la SQLi et l'injection de commande (9.8) parce qu'elle exige une action de la victime (UI:R) et n'atteint que partiellement C et I, sans A. Elle deborde en revanche du serveur (S:C), ce qui l'empeche de tomber plus bas. EOF - Committez :
git add rapports/threat-model.md git commit -m "threat model: notation CVSS de VULN-01, VULN-02, VULN-03"
- VULN-01 et VULN-03 partagent exactement le même vecteur CVSS (9.8). Est-ce à dire qu'elles présentent le même risque pour votre appli ? Qu'est-ce que CVSS ne capture pas ?
- Vous avez peut-être noté l'XSS S:U au premier essai. Qu'est-ce qui, dans la définition de Scope, impose S:C pour une XSS réfléchie ?
- Un score CVSS est censé être reproductible par un tiers. Deux analystes peuvent-ils aboutir à des vecteurs différents pour la même faille ? Sur quelles métriques, et est-ce un défaut de la méthode ?
Construisez le vecteur avant de regarder le score. Si vous partez du chiffre, vous ajusterez les métriques pour tomber dessus et le vecteur ne voudra plus rien dire. L'ordre est : huit décisions, puis le score qui en tombe.
Le calculateur FIRST ne fait que l'arithmétique — il ne juge pas à votre place si l'XSS est S:C ou S:U. C'est vous qui décidez du vecteur ; lui donne le nombre. Un score que vous ne savez pas défendre lettre à lettre est un score faux, même s'il vient du calculateur.
Intention — faire produire, sur ses propres failles, les vecteurs CVSS complets que M5 et le PFR exigeront. Le TP est réussi si Elie obtient 9.8 pour la SQLi et 6.1 pour l'XSS en sachant expliquer l'écart, pas s'il recopie deux nombres.
Ce qui coince
- Score d'abord, vecteur ensuite. Casse tout l'exercice ; exiger l'ordre inverse.
- XSS notée S:U par réflexe. C'est l'erreur pédagogique centrale : l'XSS est S:C parce que l'impact sort du serveur vers le navigateur. Le laisser se tromper, puis lui faire recalculer — l'écart 5.x vs 6.1 rend la leçon tangible.
- VULN-03 (command injection) sous-notée. Elle est 9.8 comme la SQLi : AV:N, exécution de code, impact total. Bon contrôle que le raisonnement se transfère.
Questions à anticiper
- « Pourquoi l'XSS n'est pas Critical alors qu'on en parle partout ? » — parce qu'elle dépend de la victime et n'atteint pas la dispo ; c'est le sens de la note.
- « Dois-je scorer les dix ? » — trois aujourd'hui (SQLi, XSS, command injection) ; les dix iront au registre de risques demain.
Ce qu'il faut exiger — trois vecteurs complets, l'écart 9.8 / 6.1 expliqué par UI et Scope, et le tout consigné dans threat-model.md.
TP 5 — Boucler le threat model
Jour 1 · Modéliser la menace sur devsecops-lab · 40 min
Assembler et relire threat-model.md pour qu'il tienne debout seul, sans votre code sous les yeux — c'est le livrable que le point de contrôle exige.
Vous avez tout écrit aux TP1 à 4. Il reste à en faire un document qui tient debout : un sommaire, des renvois cohérents entre le DFD, STRIDE, l'arbre et les notes CVSS, et un résumé de tête. C'est ce document, et non vos souvenirs, que Samer lira ce soir.
- Ajoutez un résumé exécutif en tête du rapport (juste après le titre) : trois phrases qu'un décideur non technique comprend. Combien de failles, la plus grave, l'état général :
# Threat model - devsecops-lab > Resume : 10 failles modelisees, dont 3 Critical (SQLi, injection de commande, > deserialisation). L'objectif d'attaque le plus grave - executer du code - est > atteignable par 4 chemins independants. Faiblesse structurelle : absence de > journalisation exploitable (Repudiation), qui compromet toute reponse a incident. - Relisez en croisant les sections. Chaque VULN citée dans le DFD ou l'attack tree apparaît-elle dans la table STRIDE ? Les trois failles notées en CVSS sont-elles les plus graves de STRIDE ? Corrigez les écarts — c'est le vrai travail de ce TP.
- Faites le test du lecteur sans code : reprenez la ligne VULN-01 et vérifiez que, sans ouvrir
app.py, on comprend où est la faille, comment la déclencher, et pourquoi elle vaut 9.8. Ajoutez la charge d'exploitation si elle manque. - Vérifiez une dernière fois le rendu des deux diagrammes Mermaid dans l'aperçu, puis poussez la branche :
git add rapports/threat-model.md git commit -m "threat model: resume executif et relecture croisee" git push -u origin feat/threat-model
- Votre résumé exécutif tient en trois phrases. Laquelle des quatre sections du rapport un décideur lira-t-il en réalité, et qu'est-ce que cela dit de ce qu'il faut y soigner ?
- Le rapport doit se lire sans le code. Qu'avez-vous dû ajouter pour cela, que vous trouviez « évident » parce que vous connaissez app.py par coeur ?
Le test du livrable : donnez-le mentalement à quelqu'un qui n'a jamais vu app.py. S'il ne peut pas, à sa seule lecture, rejouer VULN-01 et comprendre pourquoi elle est notée 9.8, le rapport n'est pas fini. Ce n'est pas la longueur qui compte, c'est l'auto-suffisance.
Intention — item de rédaction adossé aux critères du point de contrôle du soir. On ne produit rien de neuf : on rend cohérent et rejouable ce qui a été construit aux TP1-4. C'est le moment où le modèle passe de brouillon à livrable auditable.
Ce qui coince
- Incohérences entre sections : une VULN notée dans le DFD mais absente de STRIDE, ou l'inverse. La relecture croisée est l'objet du TP.
- Rapport qui suppose le code connu (« la route login ») sans jamais le rappeler. Exiger l'auto-suffisance.
- Mermaid non rendu resté en texte : à vérifier une dernière fois dans l'aperçu.
Questions à anticiper
- « C'est bon, j'ai les quatre sections ? » — la question n'est pas la présence des sections mais leur cohérence entre elles ; faire le test du lecteur sans le code.
Ce qu'il faut exiger — un threat-model.md qui se lit seul, dont les quatre sections se répondent, poussé sur la branche, prêt pour le point de 16 h 30.
M4 · J1 — DFD, STRIDE et attack tree sur devsecops-lab
Jour 1 · Modéliser la menace sur devsecops-lab · 30 min
Votre encadrant vous demandera de montrer, pas de réciter. Ayez threat-model.md ouvert et son rendu Mermaid visible. Il vous fera probablement recalculer une note CVSS de tête et désigner une case STRIDE que vous avez choisi de laisser vide.
Gardez sous la main les charges d'exploitation de votre attack tree : ce sont elles que vous rejouerez lundi, sur votre propre maquette, dans l'exercice d'incident.
Intention — vérifier que le threat model est un raisonnement ancré, pas un formulaire. Trente minutes, point quotidien : on lit le livrable et on sonde deux endroits où le formalisme creux se trahit.
Ce qu'il faut exiger — trois vérifications, faites devant vous :
- Il ouvre
threat-model.mdet vous montre une case STRIDE laissée vide et justifiée. S'il n'en a aucune, la table est un remplissage — reprendre. - Il désigne, sur l'attack tree, pourquoi la racine OU rend la correction partielle illusoire. C'est le test que l'arbre a été compris, pas décoré.
- Il recalcule le 6.1 de l'XSS de tête, en citant UI:R et S:C. S'il récite le nombre sans les métriques, CVSS n'est pas acquis.
Ce qui coince
- Le critère bâclé : la Repudiation absente de STRIDE. Sans elle, la simulation de lundi n'aura pas de fil — la réclamer ce soir.
- Feuilles d'attack tree restées au niveau catégorie : la simulation de lundi rejoue ces charges, elles doivent être exécutables dès aujourd'hui.
Questions à anticiper
- « Je finis le rapport demain matin ? » — non : lundi enchaîne sur le registre de risques qui part de ce threat model. Un modèle incomplet décale toute la journée.
Ne pas valider si — la table STRIDE n'a aucune case vide justifiée, l'attack tree a des feuilles non exécutables, ou l'écart CVSS 9.8/6.1 ne peut pas être expliqué. Chacun de ces trois signes annonce un modèle de façade.
Du modèle au registre de risques
Jour 2 · Du risque à l'incident rejoué · 30 min
Vous savez noter une faille. Noter une faille n'est pas gérer un risque. CVSS vous dit « cette injection SQL vaut 9.8 dans l'absolu » ; le registre de risques répond à la question que se pose vraiment une organisation : « compte tenu de notre contexte, qu'est-ce qu'on en fait, qui s'en occupe, et pour quand ? ».
Le registre reprend donc la note CVSS, mais y ajoute ce que CVSS ignore volontairement — parce que CVSS se veut comparable entre organisations, et que votre contexte ne l'est pas.
| Colonne | Origine | Ce qu'elle apporte |
|---|---|---|
| VULN, CWE, OWASP, STRIDE | Threat model (J1) | L'identité de la faille |
| Vecteur et score CVSS | TP4 (J1) | La gravité intrinsèque, comparable |
| Score DREAD | Nouveau (ci-dessous) | La priorité métier, pour arbitrer entre égaux |
| Traitement | Décision | Mitiger / accepter / transférer / éviter |
| Propriétaire, échéance, statut | Décision | Qui, pour quand, où on en est |
DREAD — une grille complémentaire, jamais un remplacement
DREAD cote cinq dimensions, chacune de 1 à 3 ici (faible, moyen, fort). Son total, de 5 à 15, sert à départager deux risques que CVSS place au même niveau — pas à remplacer CVSS, qui reste la note de référence du parcours.
| Lettre | Dimension | Question |
|---|---|---|
| D | Damage | Quels dégâts si l'attaque réussit ? |
| R | Reproducibility | Est-elle facile à rejouer à l'identique ? |
| E | Exploitability | Quel effort, quelle compétence pour l'exploiter ? |
| A | Affected users | Combien d'utilisateurs touchés ? |
| D | Discoverability | Est-elle facile à découvrir ? |
Deux analystes peuvent coter DREAD différemment : ses axes ne sont pas normalisés, contrairement au vecteur CVSS qu'un tiers recalcule à l'identique. C'est pourquoi le référentiel de ce parcours en fait une grille de priorisation métier complémentaire, jamais la note de sévérité. Concrètement : la sévérité, c'est CVSS ; DREAD sert à décider lequel de deux 9.8 vous corrigez d'abord.
Les quatre traitements — et le faux cinquième
- Mitiger — corriger, ou réduire la probabilité ou l'impact. Le cas général.
- Accepter — décider, en connaissance de cause et par écrit, de vivre avec. Une acceptation a toujours un propriétaire et une raison.
- Transférer — faire porter le risque par un tiers (assurance, prestataire). Le risque ne disparaît pas, il change de porteur.
- Éviter — supprimer la fonctionnalité qui porte le risque. Radical, parfois juste.
« Ignorer » n'est pas dans la liste. Ignorer, c'est accepter sans le documenter — et c'est précisément ce qu'un audit relève. Toute ligne de votre registre portera donc un traitement explicite, jamais un blanc.
CVSS note la faille ; le registre de risques note le risque pour vous — ce n'est pas la même chose. Une faille 9.8 sur une route désactivée en production est un risque faible. Le registre ajoute donc à CVSS deux dimensions que CVSS ignore : la probabilité dans votre contexte, et ce que vous décidez d'en faire.
Quatre traitements possibles, pas un de plus : mitiger (corriger ou réduire), accepter (décider de vivre avec, en le documentant), transférer (assurance, prestataire), éviter (supprimer la fonctionnalité). « Ignorer » n'est pas un traitement — c'est accepter sans le dire, et c'est ce qu'un audit sanctionne.
Intention — faire le pont entre la note technique (CVSS, acquise J1) et la décision de gestion (le registre). Introduire DREAD strictement comme grille complémentaire de priorisation métier, jamais en remplacement de CVSS — c'est une contrainte du référentiel.
Ce qui coince
- Registre = copie de la table CVSS. Non : le registre ajoute probabilité, traitement, responsable, échéance, statut. C'est un document vivant, pas une photographie.
- DREAD pris pour plus rigoureux que CVSS parce qu'il a cinq axes. C'est l'inverse : DREAD est subjectif et non normalisé ; il sert à trancher entre deux risques de CVSS voisin selon l'enjeu métier, pas à noter dans l'absolu.
- « Accepter » compris comme « laisser tomber ». Accepter est une décision tracée, avec un propriétaire — c'est ce qui la distingue de l'oubli.
Questions à anticiper
- « Si CVSS suffit, pourquoi DREAD ? » — pour arbitrer entre deux 9.8 quand on ne peut en corriger qu'un ce sprint : DREAD éclaire l'impact métier et la facilité de découverte.
- « Qui est le propriétaire du risque, ici ? » — Elie, sur sa maquette ; en entreprise, un métier. La notion de propriétaire est ce qui rend une acceptation défendable.
Ce qu'il faut exiger — qu'il distingue noter une faille (CVSS) de gérer un risque (registre), et qu'il énonce les quatre traitements sans confondre accepter et ignorer.
Quiz — CVSS, DREAD et gestion du risque
Jour 2 · Du risque à l'incident rejoué · 10 min
1. VULN-01 est notée CVSS 9.8. Dans quelle bande tombe-t-elle, et qu'implique cette bande ?
2. Dans le vecteur de VULN-01, pourquoi la métrique Attack Vector vaut-elle AV:N (Network) ?
3. Le référentiel impose CVSS comme note de référence et n'admet DREAD qu'en complément. Pourquoi ne pas s'appuyer sur DREAD seul ?
4. Pourquoi VULN-02 (XSS réfléchi) obtient-elle 6.1 quand VULN-01 (SQLi) obtient 9.8, alors que les deux sont des injections ?
5. Dans un registre de risques, que signifie exactement « accepter » un risque ?
Intention — vérifier que la note technique et la décision de gestion ne sont pas confondues, avant de construire le registre. q1 teste les bandes, q2 une métrique du vecteur, q3 le rôle relatif CVSS/DREAD, q4 la logique de l'écart XSS/SQLi, q5 la sémantique d'« accepter ».
Ce qui coince
- q3 : croire DREAD plus complet donc suffisant. C'est l'erreur que le référentiel interdit ; une faute ici impose de reprendre l'encadré de L5.
- q5 : confondre accepter et corriger, ou accepter et transférer. La distinction porte tout le registre.
Questions à anticiper
- « DREAD n'est-il pas plus fin, avec cinq axes ? » — plus d'axes ne veut pas dire plus rigoureux ; ils ne sont pas normalisés.
Ce qu'un échec révèle — q3 ou q5 fausses annoncent un registre où traitement et sévérité seront mélangés ; corriger avant le TP6.
TP 6 — Le registre de risques
Jour 2 · Du risque à l'incident rejoué · 1 h
Construire risk-register.csv : les dix VULN, leur note CVSS, une cote DREAD, un traitement décidé et un propriétaire.
rapports/risk-register.csv, à côté du threat model. M5 s'en servira pour prioriser l'exploitation, le PFR pour montrer que chaque faille a un traitement décidé. C'est un document vivant : on ne supprime pas une ligne corrigée, on en change le statut.
- Créez le fichier avec son en-tête. Une colonne par information ; les vecteurs CVSS ne contiennent pas de virgule, ce qui garde le CSV propre :
cat > rapports/risk-register.csv <<'EOF' id,titre,composant,cwe,owasp,stride,cvss_vecteur,cvss_score,cvss_bande,dread,traitement,statut,module,proprietaire EOF - Ajoutez les premières lignes. Reprenez les vecteurs calculés hier pour VULN-01/02/03 ; notez que VULN-01 et VULN-02 sont déjà corrigées (statut), et servez-vous de DREAD pour départager les deux 9.8 :
cat >> rapports/risk-register.csv <<'EOF' VULN-01,Injection SQL login,POST /login,CWE-89,A03,Spoofing,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,Critical,15,mitiger,corrige,M1-J3,Elie VULN-02,XSS reflechi,GET /search,CWE-79,A03,Tampering,CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N,6.1,Medium,9,mitiger,corrige,M2,Elie VULN-03,Injection de commande,GET /ping,CWE-78,A03,Elevation,CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H,9.8,Critical,13,mitiger,corrige,M2,Elie EOF - Complétez les sept lignes restantes (VULN-04 à VULN-10) vous-même, en vous appuyant sur la table des dix vulnérabilités du parcours pour le CWE, l'OWASP et le module de correction. Calculez chaque vecteur CVSS comme hier. Repères : IDOR (VULN-04) et SSRF (VULN-08) sont encore ouvertes, elles ne seront traitées qu'en M5 ; les CVE des dépendances (VULN-10) relèvent de la SCA de M2.
- Décidez un traitement pour chaque ligne, et justifiez au moins une acceptation. Sur une maquette locale, certaines menaces (par exemple l'absence de rate-limiting, ou une faille sur une route jamais exposée) peuvent être acceptées avec raison — à condition de l'écrire. Ajoutez ces justifications dans un fichier compagnon :
cat > rapports/risk-register-decisions.md <<'EOF' # Decisions de traitement - justifications - VULN-XX : ACCEPTEE. Motif : route non exposee sur la maquette locale, impact nul hors localhost. Proprietaire : Elie. A revoir si l'appli est exposee. EOF - Vérifiez que le CSV est bien formé — dix lignes de données, aucune virgule parasite :
column -s, -t rapports/risk-register.csv | less -S # ou ouvrez-le dans un tableur ; toutes les colonnes doivent s'aligner - Committez le registre et ses justifications :
git add rapports/risk-register.csv rapports/risk-register-decisions.md git commit -m "risk register: 10 VULN notees, traitees et attribuees"
- VULN-01 et VULN-03 ont le même score CVSS (9.8). Sur quel axe DREAD les avez-vous départagées, et pourquoi ce choix se défend-il ?
- Vous avez « accepté » au moins un risque. Qu'est-ce qui distingue votre acceptation documentée d'un simple oubli, aux yeux d'un auditeur ?
- VULN-04 (IDOR) et VULN-08 (SSRF) restent ouvertes jusqu'à M5. Est-il cohérent de les laisser « ouvertes » dans un registre, ou faudrait-il un statut intermédiaire ?
Le registre n'est pas la table CVSS d'hier recopiée. Deux colonnes le distinguent et sont l'objet du TP : le traitement (mitiger/accepter/transférer/éviter) et le statut (ouvert/corrigé/en cours). Une ligne sans traitement décidé n'est pas une ligne de registre, c'est une observation.
Le format CSV n'est pas un caprice : un registre se trie, se filtre, se diffe dans Git. Gardez un séparateur propre et pas de virgule dans les cellules — d'où les vecteurs CVSS qui n'en contiennent pas.
Intention — produire risk-register.csv, livrable réutilisé par M5 et le PFR. L'exercice force la décision : pour chaque VULN, un traitement et un propriétaire, pas seulement une note.
Ce qui coince
- Registre = copie de la table CVSS. Contrôler que traitement et statut sont renseignés pour les dix lignes, pas seulement CVSS.
- Toutes les VULN mises à « mitiger » par défaut. Or certaines sont déjà corrigées (statut = corrigé, cf. modules M1-M3) et une au moins peut légitimement être « acceptée » sur une maquette locale — lui faire justifier au moins une acceptation.
- Virgules dans les cellules qui cassent le CSV. Vérifier à l'ouverture tableur ou par
column -s, -t. - DREAD coté au hasard. Exiger qu'il serve à quelque chose : départager deux 9.8 (SQLi vs command injection) par la découvrabilité ou l'impact.
Questions à anticiper
- « Statut corrigé, alors je supprime la ligne ? » — non : le registre garde la mémoire ; on passe le statut à corrigé avec le module qui l'a fait.
- « Qui est propriétaire ? » — Elie ici ; le champ existe pour ne pas laisser un risque orphelin.
Ce qu'il faut exiger — dix lignes complètes, traitement et propriétaire partout, au moins une acceptation argumentée, un CSV qui s'ouvre proprement.
Répondre à un incident
Jour 2 · Du risque à l'incident rejoué · 30 min
Un threat model dit ce qui peut arriver. La réponse à incident traite ce qui arrive. Les deux se répondent : un incident bien géré part toujours d'une préparation faite à froid — les journaux qu'on a pensé à collecter, les playbooks qu'on a écrits avant d'en avoir besoin. C'est la quatrième question de Shostack retournée : « a-t-on fait du bon travail ? » se vérifie le jour où quelqu'un attaque.
Les quatre phases (NIST SP 800-61)
- Préparation. Avant tout incident : journalisation en place, playbooks écrits, rôles connus, sauvegardes. C'est la phase qui décide du succès des trois autres.
- Détection et analyse. Repérer qu'il se passe quelque chose, comprendre quoi, jusqu'où. Sans journaux, cette phase est aveugle.
- Confinement, éradication et reprise. Arrêter la propagation, retirer la cause, puis remettre en service sans revenir à l'état vulnérable.
- Activité post-incident. Ce qu'on a appris, ce qu'on change. La boucle qui évite la répétition.
Le cadre SANS découpe le même cycle en six : Preparation, Identification, Containment, Eradication, Recovery, Lessons learned. Plus granulaire, il se prête mieux à l'écriture d'un playbook, où chaque étape devient une section. On l'utilisera pour structurer le vôtre ; retenez que c'est le même cycle que NIST, découpé plus fin.
Ce qu'est un playbook
Un playbook n'est pas un cours : c'est une procédure faite pour être suivie sous pression, par quelqu'un qui n'a pas le temps de réfléchir. D'où sa forme : un déclencheur clair, des étapes numérotées, des décisions binaires (« si le secret a été poussé sur GitHub → le révoquer immédiatement »), et pas de prose. Vous en écrirez un au TP suivant, puis vous le suivrez pour de vrai dans la simulation.
Rappelez-vous la case R de votre table STRIDE : devsecops-lab ne journalise presque rien. La phase de détection et analyse repose entièrement sur les journaux. Une appli qui ne journalise pas rend la réponse à incident aveugle — vous allez le vivre dans quelques minutes, en tentant de reconstruire une attaque à partir des seules lignes de Werkzeug. C'est là que la menace la plus discrète de votre threat model montre son vrai coût.
Intention — donner le cadre de la réponse à incident (NIST 4 phases, mnémonique PICERL) et surtout relier la préparation à la Repudiation de STRIDE : sans journaux, pas de détection ni d'analyse. C'est le pont vers la simulation qui suit.
Ce qui coince
- Réponse à incident réduite au confinement (« on débranche »). Or l'essentiel est avant (préparation, détection) et après (leçons). Le confinement est visible, pas central.
- Éradication et reprise confondues. Éradiquer = retirer la cause (le code vulnérable, l'accès de l'attaquant) ; reprise = remettre en service proprement, en vérifiant qu'on n'est pas revenu à l'état vulnérable.
- Playbook pris pour de la doc morte. Un playbook est fait pour être suivi sous pression, à froid : étapes numérotées, décisions binaires, pas de prose.
Questions à anticiper
- « NIST ou SANS ? » — deux découpages du même cycle ; PICERL (SANS, six étapes) est plus granulaire pour écrire un playbook, NIST (quatre phases) plus simple à retenir. On s'en sert de l'un et on cite l'autre.
- « SOAR, c'est pour nous ? » — automatiser la réponse suppose déjà des playbooks écrits et testés ; on en pose les fondations, l'automatisation vient après.
Ce qu'il faut exiger — qu'il situe la préparation (journaux, playbooks) comme la phase qui décide du succès des suivantes, et qu'il relie explicitement à la Repudiation identifiée J1.
TP 7 — Écrire un playbook
Jour 2 · Du risque à l'incident rejoué · 55 min
Rédiger un playbook de réponse à incident suivant les six étapes PICERL, pour un scénario tiré de vos propres failles.
On écrit le playbook du secret exposé (VULN-05) : un identifiant poussé dans le dépôt, détecté par Gitleaks. C'est le cas où l'éradication est la plus instructive — un secret entré dans l'historique Git n'en sort pas en effaçant une ligne. Vous suivrez ce playbook, en partie, dans la simulation qui suit.
- Créez le playbook et posez son en-tête : déclencheur, périmètre, gravité attendue. Le déclencheur doit être sans ambiguïté :
cat > rapports/playbook-secret-expose.md <<'EOF' # Playbook - secret expose dans le depot (VULN-05) Declencheur : Gitleaks (pre-commit ou CI) signale un secret, OU une clef en clair est reperee dans la source / l'historique. Perimetre : depot devsecops-lab. Gravite : elevee (un secret pousse est compromis). EOF - Écrivez les six sections PICERL, à l'impératif, avec des décisions binaires. Voici la trame ; complétez chaque étape avec la commande exacte :
cat >> rapports/playbook-secret-expose.md <<'EOF' ## 1. Preparation (a froid, deja en place) - Hook Gitleaks en pre-commit + job 'secrets' en CI (M1). - .gitignore couvre .env ; .env.exemple versionne a vide. ## 2. Identification - [ ] Confirmer : quel secret, quelle valeur, depuis quel commit ? git log -p -S '<fragment_du_secret>' -- <fichier> - [ ] Le secret a-t-il ete POUSSE sur le distant ? (si oui -> compromis definitif) ## 3. Containment - [ ] Si pousse : considerer la clef comme publique des maintenant. - [ ] Bloquer la fuite en cours : ne plus committer ; geler la branche. ## 4. Eradication (deux gestes distincts, dans cet ordre) - [ ] REVOQUER la clef chez le fournisseur (elle ne doit plus rien valoir). - [ ] PURGER l'historique : git filter-repo --invert-paths --path <fichier> (ou BFG) ; retirer la valeur du code, la remplacer par une variable d'env. ## 5. Recovery - [ ] Re-scanner : gitleaks detect --no-git ; doit ne rien trouver. - [ ] Emettre une NOUVELLE clef, la stocker hors du depot (.env non versionne). - [ ] Forcer le re-clone chez les collaborateurs (l'historique a change). ## 6. Lessons learned - [ ] Pourquoi le hook n'a pas bloque ? (installe ? contourne par --no-verify ?) - [ ] Consigner l'incident dans journal-incidents.md. EOF - Relisez avec l'oeil de quelqu'un sous pression : chaque case à cocher est-elle une action unique et sans ambiguïté ? Réécrivez toute étape qui contient un « il faudrait » ou un « selon le cas » sans dire lequel.
- Committez le playbook :
git add rapports/playbook-secret-expose.md git commit -m "playbook: reponse au secret expose (VULN-05), forme PICERL"
- Votre playbook sépare « révoquer la clé » et « purger l'historique ». Pourquoi l'ordre compte-t-il, et que se passe-t-il si on purge sans révoquer ?
- La reprise contient un re-scan Gitleaks. Sans lui, comment sauriez-vous que l'éradication a réussi ? Que prouve exactement ce re-scan, et que ne prouve-t-il pas ?
- Un playbook est fait pour être suivi sous pression. Quelle étape du vôtre risque le plus d'être bâclée le jour J, et comment l'avez-vous rendue difficile à sauter ?
Le test d'un bon playbook : donnez-le à quelqu'un de stressé qui n'a pas participé à l'écriture. S'il hésite sur une étape, c'est qu'elle contient de la prose au lieu d'une action. Numérotez, impérativez, tranchez.
Écrivez le playbook avant la simulation, pas pendant. Tout l'intérêt de l'exercice de tout à l'heure est de suivre une procédure écrite à froid — et de constater où elle vous laisse tomber.
Intention — produire un playbook réel (livrable réutilisé M5/PFR) et faire expérimenter la forme « procédure sous pression ». Le scénario du secret exposé (VULN-05) est idéal : il a une éradication non triviale (purge d'historique Git) qu'on ne s'improvise pas.
Ce qui coince
- Playbook en prose. Le refuser : étapes numérotées, verbes à l'impératif, décisions binaires.
- Éradication d'un secret réduite à « supprimer la ligne du code ». Non : la clé est dans l'historique Git, il faut la révoquer (elle est déjà compromise) ET la purger (git filter-repo / BFG). Distinguer révoquer et purger.
- Reprise oubliée : après purge, re-scanner (Gitleaks) pour prouver que le secret n'est plus là — sinon on ne sait pas si l'éradication a marché.
- Confusion « révoquer la clé » / « la retirer du code ». Révoquer se fait côté fournisseur (la clé ne vaut plus rien) ; retirer, côté dépôt.
Questions à anticiper
- « Sur une maquette, révoquer une clé factice a-t-il un sens ? » — le geste est simulé, mais l'ordre (révoquer AVANT de purger) est le vrai enseignement : un secret poussé est compromis pour toujours.
- « Un seul playbook suffit ? » — un complet vaut mieux que trois esquisses ; on peut en amorcer un second (SQLi) si le temps le permet.
Ce qu'il faut exiger — un playbook PICERL en six sections, à l'impératif, où l'éradication distingue révoquer et purger, et où la reprise contient une vérification par re-scan.
TP 8 — Jouer l'incident sur votre maquette
Jour 2 · Du risque à l'incident rejoué · 1 h 10
Rejouer une attaque réelle sur votre propre devsecops-lab, la détecter dans les journaux, appliquer le cadre de réponse et reconstruire la chronologie — en mesurant ce que l'absence de journalisation vous coûte.
Comme posé en M1 : vous n'attaquez que ce que vous déployez vous-même, sur votre poste. Cet exercice se déroule entièrement sur localhost, sur votre maquette devsecops-lab. C'est précisément parce que la cible est la vôtre que rejouer l'attaque est légal. Rien, ici, ne sort de votre machine.
- Déployez la version vulnérable de l'appli — l'incident n'a pas de sens sur le code corrigé. Utilisez l'image
:devconstruite en M1, ou revenez au commit d'avant-correction sur une branche jetable :docker run -d --name lab-incident --network dso-net -p 5000:5000 devsecops-lab:dev # alternative : git checkout -b incident-demo <commit_avant_fix_VULN-01> - Dans un terminal, suivez les journaux du conteneur — c'est votre unique source d'analyste. Laissez-le ouvert :
docker logs -f lab-incident - Casquette attaquant. Rejouez la charge SQLi de votre attack tree contre votre propre appli. Notez l'heure exacte :
date +%H:%M:%S curl -s -X POST http://localhost:5000/login --data "user=admin'--&pwd=x" # reponse attendue : bienvenue (HTTP 200) sans mot de passe valide - Enchaînez une deuxième action pour épaissir l'incident : lisez un profil (IDOR, VULN-04) ou testez la commande (VULN-03). Notez encore l'heure :
curl -s http://localhost:5000/profile/1 curl -s "http://localhost:5000/ping?host=127.0.0.1;id" - Casquette analyste. Vous n'avez que le terminal des journaux. Sans vous servir de ce que vous savez, reconstruisez ce que les logs permettent d'affirmer : quelles requêtes, à quelle heure, quel code de retour. Écrivez cette chronologie dans un rapport d'incident :
cat > rapports/incident-2026-08-17.md <<'EOF' # Incident (exercice) - 2026-08-17 ## Chronologie reconstruite depuis les seuls journaux | Heure | Ce que le journal montre | Ce qu'il ne montre PAS | |-------|--------------------------|------------------------| | hh:mm | POST /login -> 200 | le corps : la charge d'injection est invisible | | hh:mm | GET /profile/1 -> 200 | si d'autres id ont ete enumerees | | hh:mm | GET /ping -> 200 | la commande reellement executee | EOF - Faites l'inventaire du manque. Sous la table, listez les questions auxquelles vous ne pouvez pas répondre avec les journaux actuels, et reliez-les à la case Repudiation de votre threat model :
cat >> rapports/incident-2026-08-17.md <<'EOF' ## Ce que l'access log ne permet pas de conclure - Impossible de prouver la charge SQLi : le corps du POST n'est pas journalise. - Impossible de savoir quelles donnees ont ete lues (pas de log applicatif). - Lien direct avec STRIDE-Repudiation (threat-model.md, section 2). ## Recommandation Journalisation applicative structuree au niveau des routes (qui, quoi, resultat), distincte de l'access log Werkzeug. Sans elle, la detection reste aveugle. EOF - Appliquez, pour la forme, la fin du cycle : l'éradication de VULN-01 a déjà eu lieu en M1-J3 (requête paramétrée). Vérifiez-le en rejouant la charge sur la version corrigée, qui doit renvoyer 401 — c'est votre étape de reprise. Puis arrêtez le conteneur d'incident :
docker stop lab-incident && docker rm lab-incident - Consignez l'incident dans le journal du dépôt et committez :
git add rapports/incident-2026-08-17.md git commit -m "incident (exercice): SQLi rejouee, chronologie et angle mort de journalisation"
- Vous avez reconstruit la chronologie à partir de l'access log seul. Quelle est la chose la plus importante que vous n'avez pas pu prouver, et pourquoi est-ce grave dans un vrai incident (au-delà de la technique) ?
- La case Repudiation de votre threat model, écrite vendredi, s'est matérialisée aujourd'hui. En quoi cet exercice change-t-il la façon dont vous auriez priorisé cette menace dans le registre de risques ?
- Votre playbook du TP précédent portait sur le secret exposé, pas sur la SQLi. À la lumière de cet exercice, quelle section d'un playbook « intrusion applicative » serait la plus dure à exécuter sur devsecops-lab en l'état ?
Le résultat le plus important de ce TP n'est pas « l'attaque marche » — vous le savez déjà. C'est ce que vous ne trouvez pas dans les journaux. Notez chaque question à laquelle les logs de Werkzeug ne répondent pas : quel corps de requête, quelle donnée exfiltrée, quelle session. Cette liste de trous est votre vraie découverte.
Vous jouez les deux rôles : attaquant (vous connaissez la charge, c'est celle de votre attack tree) puis analyste (vous ne disposez que des journaux). Séparez bien les deux casquettes — l'analyste n'a pas le droit de « savoir » ce que l'attaquant a fait.
Intention — la quatrième question de Shostack, éprouvée : on attaque pour de vrai (sur sa propre maquette), on tente de détecter, et on bute sur la Repudiation identifiée J1. L'objectif pédagogique est le manque : Elie doit constater, mesure à l'appui, qu'on ne peut pas reconstruire l'attaque avec les journaux existants.
Ce qui coince
- Il attaque la version corrigée et s'étonne que la SQLi renvoie 401. Il faut jouer l'incident sur la version vulnérable : image
devsecops-lab:devou checkout du commit d'avant-correction. Le lui faire choisir consciemment. - Il triche : en analyste, il « sait » déjà la charge. Lui imposer de ne raisonner que sur les journaux, sinon la démonstration du manque s'effondre.
- Il conclut « pas de logs » un peu vite. Werkzeug journalise la ligne d'accès (méthode, chemin, code). Le vrai constat est plus fin : on voit qu'un POST /login a eu lieu, mais pas le corps, donc pas la charge d'injection. C'est ce niveau de constat qu'on exige.
- Périmètre : rappeler que tout se passe sur localhost, sur sa maquette. C'est légal précisément parce que c'est la sienne.
Questions à anticiper
- « Comment j'aurais pu voir la charge ? » — journalisation applicative structurée (logging au niveau route), pas juste l'access log. C'est la recommandation de sortie.
- « Dois-je vraiment corriger dans ce TP ? » — l'éradication est déjà faite (M1-M2) ; ici on rejoue et on documente, on ne recorrige pas.
Ce qu'il faut exiger — une chronologie reconstruite à partir des seuls journaux, la liste explicite de ce qui manque pour la compléter, et la recommandation « journaliser au niveau applicatif » reliée à la case R de STRIDE.
TP 9 — Boucler les livrables du module
Jour 2 · Du risque à l'incident rejoué · 45 min
Rendre cohérents et rejouables les trois livrables du module — threat model, registre, playbook et incident — avant la revue de fin de module.
Quatre fichiers dans rapports/ : threat-model.md, risk-register.csv (+ ses décisions), playbook-secret-expose.md et incident-2026-08-17.md. La revue de cet après-midi vérifie qu'ils racontent la même histoire.
- Passe de cohérence. Vérifiez, VULN par VULN, que le threat model et le registre s'accordent : même score CVSS, même statut. Vérifiez que l'incident rejoué (SQLi) et le playbook renvoient à des menaces présentes dans le threat model. Corrigez les écarts.
- Mettez à jour
journal-incidents.md: ajoutez la ligne de l'exercice d'aujourd'hui (symptôme, cause, correction, temps). Le journal est la mémoire du dépôt, il doit refléter la simulation.# ajoutez une ligne au tableau existant, par exemple : # | 2026-08-17 | SQLi rejouee en exercice | access log insuffisant | reco journalisation applicative | 0 | - Complétez
reponses-reflexion.md: une section par TP du module, la question recopiée, votre réponse en trois à cinq lignes. C'est là que Samer puisera ses questions cet après-midi. - Ouvrez la Pull Request vers
main. La branche est protégée : la PR est le seul chemin, et elle déclenche les cinq jobs que vous avez construits depuis le 3 août —secretsetsast(M1),scaetdast(M2),image(M3). Vérifiez qu'ils passent tous au vert :git add -A git commit -m "M4: alignement des livrables, journal et reflexions a jour" git push # puis ouvrez la PR feat/threat-model -> main sur GitHub
- En alignant le registre et le threat model, avez-vous trouvé une incohérence ? Laquelle, et qu'est-ce qu'elle révèle sur la façon dont vous avez travaillé pendant deux jours ?
- La PR vers main déclenche les cinq portes du pipeline. Pourquoi est-il pertinent qu'un livrable documentaire passe malgré tout par les mêmes garde-fous que du code ?
La cohérence entre livrables est ce qu'on regarde en revue : une VULN notée 9.8 dans le threat model mais absente du registre, ou un incident qui invoque une menace jamais modélisée, sont les défauts qui sautent aux yeux. Faites la passe croisée maintenant, pas pendant la revue.
Intention — item de rédaction adossé aux critères de la revue de fin de module. On ne crée rien : on aligne threat model, registre, playbook et rapport d'incident pour qu'ils se répondent, et on met à jour le journal et les réflexions.
Ce qui coince
- Livrables cohérents chacun pris seul mais qui se contredisent entre eux. La passe croisée est l'objet du TP.
- journal-incidents.md et reponses-reflexion.md oubliés — ce sont pourtant les documents que Samer lit en priorité en revue.
- Tout laissé sur la branche sans PR : la revue porte aussi sur la boucle Git (branche -> PR -> main protégée) vue en M1.
Ce qu'il faut exiger — quatre livrables cohérents entre eux, journal et réflexions à jour, et une PR ouverte vers main.
M4 · Bilan — Threat model, registre de risques et incident rejoué
Jour 2 · Du risque à l'incident rejoué · 2 h
Elle a lieu lundi à 15 h 00 et dure deux heures — c'est la durée portée par ce point de contrôle. Votre encadrant partira de votre reponses-reflexion.md et de vos quatre livrables ; il vous fera rejouer l'incident et défendre vos décisions de traitement.
Ce qui compte n'est pas la longueur des documents, mais qu'ils racontent la même histoire et que vous sachiez la défendre page fermée. Le threat model et le registre que vous validez aujourd'hui sont les entrées directes du pentest de M5 : vous ne repartez pas de zéro demain.
Intention — la revue de fin de module, deux heures, lundi 15 h. Elle porte sur les deux jours : le threat model tient-il debout, le registre décide-t-il vraiment quelque chose, et Elie a-t-il vécu le coût de la Repudiation dans la simulation ? C'est le seul moment où l'on vérifie que le module n'a pas produit des documents morts.
Un déroulé qui tient en deux heures
- 30 min de démonstration : il rejoue la SQLi sur sa maquette, montre la chronologie reconstruite et désigne, journaux à l'appui, ce qu'il ne peut pas prouver.
- 40 min de questions tirées de
reponses-reflexion.mdet du threat model : recalcul d'une note CVSS, lecture de l'attack tree, justification d'une acceptation de risque. - 20 min sur le registre : au moins une acceptation argumentée, l'usage de DREAD pour départager deux 9.8.
- Le reste sur le chaînage vers M5 : le threat model et le registre sont les entrées du pentest de demain.
Les trois choses à faire défendre, page fermée — la distinction CVSS (sévérité de la faille) / registre (risque pour nous) ; pourquoi la racine OU de l'attack tree interdit une correction partielle ; et pourquoi l'absence de journalisation applicative est la menace la plus coûteuse, alors qu'elle n'a pas de VULN. Si les documents sont parfaits mais qu'aucune de ces trois idées n'est défendue, le module n'est pas acquis — c'est la distinction que ce point de contrôle existe pour faire.
Ce qui est souvent en retard — le rapport d'incident et les justifications d'acceptation. Les réclamer avant la séance, pas pendant.
Questions à anticiper
- « Est-ce que M5 repart de zéro ? » — non : le threat model et le registre sont les entrées du pentest. Le dire pour qu'il ne « nettoie » pas ses rapports.
- « La simulation était factice, non ? » — l'attaque était réelle sur une vraie appli vulnérable ; seul le contexte (sa maquette) était contrôlé. C'est le sens de l'exercice.
Ne pas valider si — le registre est une simple copie de la table CVSS (aucune décision de traitement), ou si la chronologie d'incident ne distingue pas ce que les journaux montrent de ce qu'ils taisent. Sur ce dernier point, le juge est la table à deux colonnes du rapport d'incident, pas le récit oral.