DevSecOps : sécuriser le code, les dépendances et les conteneurs
Quitter

Leçon 1 sur 11 · Fondamentaux

Shift-left et OWASP Top 10 : la sécurité commence dans l'éditeur

45 min Node.js 22 (Debian, git, npm, python3)

Objectif

Comprendre pourquoi la sécurité doit entrer dans le travail des développeurs dès la première ligne de code (le « shift-left »), et savoir reconnaître, dans un vrai fichier source, les grandes familles de risques décrites par l'OWASP Top 10:2025.

L'essentiel

  • Plus une faille est trouvée tard, plus elle coûte cher : une ligne corrigée dans l'éditeur coûte quelques minutes ; la même faille exploitée en production coûte une fuite de données, une déclaration à la CNDP et la confiance des clients.
  • Le DevSecOps ajoute la sécurité à chaque étape du cycle DevOps (planifier, coder, construire, tester, livrer, exploiter), automatisée autant que possible et partagée par toute l'équipe.
  • Shift-left = déplacer les contrôles vers la gauche du cycle : modélisation des menaces, analyse du code, des dépendances et des secrets avant la fusion, pas après la mise en ligne.
  • L'OWASP Top 10 est la liste de référence des dix risques les plus graves des applications web. La version 2025 commence par le contrôle d'accès défaillant (A01) et fait entrer la chaîne d'approvisionnement logicielle (A03).
  • Un outil ne remplace pas le raisonnement : il faut savoir lire un défaut, le classer et le corriger à la racine.

1. Pourquoi « shift-left » ?

Imaginez le cycle de vie d'une application dessiné de gauche à droite : on conçoit, on code, on construit, on teste, on déploie, on exploite. Pendant longtemps, la sécurité vivait tout à droite : une fois l'application terminée, un auditeur lançait un test d'intrusion quelques jours avant la mise en ligne, rendait un rapport de 60 pages… et l'équipe découvrait qu'il fallait revoir l'authentification entière.

Prenons une fintech de Casablanca qui développe un portefeuille mobile. Voici le même défaut — un mot de passe stocké en MD5 — découvert à quatre moments différents :

Moment de la découverte Ce qu'il faut faire Coût approximatif
Dans l'éditeur (extension ou revue de code) Changer deux lignes Quelques minutes
Dans la CI, avant la fusion Corriger, relancer le pipeline Une heure
Au test d'intrusion avant lancement Corriger, migrer les comptes de test, retester, décaler la date Plusieurs jours
Après une fuite en production Forcer la réinitialisation de tous les mots de passe, enquêter, informer la CNDP et les clients, communiquer Des semaines, de l'argent, de la réputation

Le shift-left consiste à pousser les contrôles vers la gauche : on ne supprime pas l'audit final, mais on fait en sorte qu'il ne trouve presque plus rien.

Ce que cela change concrètement

# Avant : la sécurité arrive à la fin, une fois par an
#   code → build → déploiement → (6 mois plus tard) audit → panique

# Après : chaque étape porte son petit contrôle automatique
#   éditeur      : extension Semgrep, alerte sur un secret collé dans le code
#   pre-commit   : gitleaks refuse un commit contenant une clé
#   pull request : SAST (Semgrep), audit des dépendances (npm audit, pip-audit)
#   build        : scan de l'image (Trivy), SBOM (Syft), signature (cosign)
#   déploiement  : refus d'une image non signée ou porteuse d'une faille critique
#   production   : journaux, alertes, correctifs rapides

Vous verrez chacun de ces outils dans la suite de la formation.

2. DevSecOps : une culture avant d'être des outils

« DevSecOps » n'est pas un poste ni un logiciel que l'on achète. C'est une manière de travailler qui repose sur trois idées.

  1. La sécurité est l'affaire de tous. Le développeur qui écrit la requête SQL est le mieux placé pour la protéger. L'équipe sécurité (quand elle existe) devient un soutien : elle fournit des règles, des modèles et des formations, au lieu de jouer le gendarme en fin de projet.
  2. Automatiser ce qui peut l'être. Un humain oublie ; un pipeline, non. Tout contrôle répétitif (secrets, dépendances, image Docker) doit tourner seul à chaque modification.
  3. Des retours rapides et utiles. Une alerte doit arriver pendant que le développeur a encore le code en tête, avec une explication et une piste de correction. Mille alertes non triées, en revanche, sont ignorées : mieux vaut peu de règles fiables que beaucoup de bruit.

Le cadre légal marocain

Au Maroc, la loi 09-08 protège les données à caractère personnel. Le responsable d'un traitement (une banque, une boutique en ligne, une administration qui propose un téléservice) doit prendre des mesures techniques et organisationnelles appropriées pour protéger ces données contre la destruction, la perte, l'altération ou l'accès non autorisé. La CNDP (Commission nationale de contrôle de la protection des données à caractère personnel) contrôle ces obligations.

Autrement dit : une injection SQL qui expose les CIN et les numéros de téléphone de vos clients n'est pas seulement un « bug ». C'est un manquement à une obligation légale. Le DevSecOps vous donne des preuves concrètes que vous avez pris ces mesures : règles d'analyse, rapports de scan, historique des corrections.

3. L'OWASP et son Top 10

L'OWASP (Open Worldwide Application Security Project) est une fondation à but non lucratif qui publie gratuitement des guides, des outils et des standards de sécurité applicative. Son document le plus connu, le Top 10, classe les risques les plus graves observés dans les applications web, à partir de données réelles fournies par des entreprises et de l'avis de la communauté.

La version 2025 est la suivante :

Code Risque Exemple marocain
A01 Contrôle d'accès défaillant (Broken Access Control) Sur un portail de e-commerce, changer /commandes/1041 en /commandes/1042 affiche la commande d'un autre client.
A02 Mauvaise configuration de sécurité (Security Misconfiguration) Un tableau de bord d'administration laissé public avec le compte admin/admin, un compartiment de stockage ouvert à tous.
A03 Défaillances de la chaîne d'approvisionnement logicielle (Software Supply Chain Failures) Une bibliothèque npm abandonnée, ou une image Docker latest qui change sans prévenir et embarque un composant piégé.
A04 Défaillances cryptographiques (Cryptographic Failures) Mots de passe hachés en MD5, numéros de carte stockés en clair, site sans HTTPS.
A05 Injection (Injection) Le champ « numéro de CIN » d'un formulaire administratif est collé tel quel dans une requête SQL.
A06 Conception non sécurisée (Insecure Design) Un code de vérification à 4 chiffres envoyé par SMS, sans limite d'essais : on le devine en quelques minutes.
A07 Défaillances d'authentification (Authentication Failures) Pas de verrouillage après des essais ratés, sessions qui n'expirent jamais.
A08 Défaillances d'intégrité des logiciels ou des données (Software or Data Integrity Failures) Une mise à jour téléchargée et installée sans vérifier sa signature.
A09 Défaillances de journalisation et d'alerte (Security Logging and Alerting Failures) Une attaque par force brute dure trois jours sans qu'aucune alerte ne parte.
A10 Mauvaise gestion des conditions exceptionnelles (Mishandling of Exceptional Conditions) Une erreur renvoie au client la pile d'appels complète, ou une exception laisse une transaction à moitié validée.

Retenez les deux nouveautés de 2025 : la chaîne d'approvisionnement (A03) — tout ce que vous n'écrivez pas vous-même, mais que vous livrez quand même — et la gestion des situations exceptionnelles (A10). Ce sont précisément deux sujets que le DevSecOps traite avec des outils automatiques (audit des dépendances, SBOM, signatures, tests d'erreurs).

Le Top 10 est un outil de sensibilisation, pas une liste exhaustive. Pour vérifier une application point par point, l'OWASP propose un standard plus complet : l'ASVS (Application Security Verification Standard).

4. Lire du code avec les yeux d'un attaquant

Regardons un extrait typique, très proche de celui du lab. C'est l'API d'une fintech : un client connecté consulte une transaction.

async function voirTransaction(req, res, db) {
  const lignes = await db.query(`SELECT * FROM transactions WHERE id = ${req.params.id}`);
  res.json(lignes[0]);
}

Deux défauts se cachent dans ces trois lignes.

Défaut 1 — A05 Injection. La valeur req.params.id vient de l'URL, donc de l'attaquant. Elle est collée dans le texte SQL. Si l'on demande /transactions/0 OR 1=1, la requête devient ... WHERE id = 0 OR 1=1 et renvoie la première transaction de la base, quelle qu'elle soit. Avec un peu plus d'imagination (UNION SELECT ...), on lit d'autres tables.

La correction : une requête paramétrée. Le texte SQL contient des marques ? et les valeurs voyagent à part ; le pilote de base de données ne les interprète jamais comme du SQL.

const lignes = await db.query(
  "SELECT * FROM transactions WHERE id = ? AND client_id = ?",
  [req.params.id, req.user.id]          // valeurs envoyées séparément
);

Défaut 2 — A01 Contrôle d'accès défaillant. Même sans injection, la première version renvoie la transaction demandée sans vérifier qu'elle appartient au client connecté. C'est le défaut le plus fréquent du Top 10 (on l'appelle aussi IDOR, Insecure Direct Object Reference). La correction ci-dessus le traite aussi : la condition AND client_id = ? limite la recherche aux transactions de l'utilisateur authentifié.

Le stockage des mots de passe

function hacherMotDePasse(mdp) {
  return crypto.createHash("md5").update(mdp).digest("hex");
}

A04 Défaillances cryptographiques. MD5 est une fonction de hachage rapide et cassée. Rapide est ici un défaut : une carte graphique calcule des milliards d'empreintes MD5 par seconde, et sans sel, deux clients qui ont le même mot de passe (Casa2026!) ont la même empreinte, que l'on retrouve dans des tables précalculées.

Il faut une fonction lente et salée, conçue pour les mots de passe : scrypt, Argon2id ou bcrypt. Node.js fournit scrypt sans dépendance :

const crypto = require("node:crypto");

function hacherMotDePasse(mdp) {
  const sel = crypto.randomBytes(16);                     // sel aléatoire, unique par compte
  const empreinte = crypto.scryptSync(mdp, sel, 64);      // dérivation volontairement coûteuse
  return sel.toString("hex") + ":" + empreinte.toString("hex");   // on stocke les deux
}

Pour vérifier un mot de passe à la connexion, on relit le sel stocké, on recalcule l'empreinte et on compare avec crypto.timingSafeEqual (comparaison en temps constant).

Les erreurs qui bavardent

function gererErreur(err, req, res, next) {
  res.status(500).json({ erreur: err.message, pile: err.stack });
}

A10 Mauvaise gestion des conditions exceptionnelles (on le classait autrefois en mauvaise configuration, A02). La pile d'appels révèle les chemins des fichiers, les versions des bibliothèques, parfois une requête SQL complète : un cadeau pour l'attaquant. On journalise le détail côté serveur et on renvoie un message neutre :

function gererErreur(err, req, res, next) {
  console.error(err);                                     // le détail reste dans les journaux
  res.status(500).json({ erreur: "Erreur interne, réessayez plus tard" });
}

5. Quel outil pour quel risque ?

Aucun outil ne couvre tout le Top 10. Voici la carte de ce que vous allez apprendre et de ce que chaque outil attrape :

Pratique (leçon) Ce qu'elle détecte surtout Catégories visées
Modélisation des menaces STRIDE (1.2) Défauts de conception, oublis de contrôle d'accès A01, A06
Détection de secrets — Gitleaks (1.3) Clés d'API, mots de passe commités A04, A07
Analyse statique — Semgrep (2.1) Injections, cryptographie faible, erreurs A05, A04, A10
Audit des dépendances — npm audit, pip-audit (2.2) Bibliothèques vulnérables A03
SBOM — Syft (2.3) Inventaire de tout ce que vous livrez A03
Scan d'images et d'IaC — Trivy (3.1) Paquets vulnérables, configurations dangereuses A02, A03
Dockerfile durci (3.2) Conteneurs root, images trop grosses A02
Signatures — cosign (3.3) Artefacts falsifiés A08
Portes dans la CI (3.4) Tout ce qui précède, à chaque modification toutes

Ce que les outils trouvent mal : les défauts de logique métier (un remboursement que l'on peut demander deux fois, un contrôle d'accès « presque » correct). Pour ceux-là, il reste la modélisation des menaces, la revue de code et les tests.

6. Par où commencer dans une petite équipe ?

Une start-up de trois développeurs à Rabat n'a ni budget ni équipe sécurité. Un ordre réaliste :

# Semaine 1 : arrêter l'hémorragie
#   - gitleaks en pre-commit et en CI (les secrets fuient en premier)
#   - npm audit / pip-audit dans la CI, seuil « critical »
# Semaine 2 : le code
#   - Semgrep avec un jeu de règles standard, en mode « avertissement » d'abord
# Semaine 3 : les conteneurs
#   - Dockerfile non root, image de base précise, scan Trivy de l'image
# Ensuite : modélisation des menaces de chaque nouvelle fonctionnalité sensible,
#           SBOM et signature des livraisons, puis durcissement des seuils

L'important est de commencer petit et de ne jamais reculer : une fois qu'une porte bloque les failles critiques, on ne la retire plus.

Pièges fréquents

  • Croire qu'un outil suffit. Un scanner vert ne prouve pas que l'application est sûre ; il prouve seulement qu'il n'a rien trouvé de ce qu'il sait chercher.
  • Activer toutes les règles le premier jour. Des centaines d'alertes non triées découragent l'équipe, qui finit par désactiver l'outil. Commencez par les sévérités élevées.
  • Corriger le symptôme, pas la cause. Filtrer le mot OR dans un champ ne corrige pas une injection ; seule la requête paramétrée le fait.
  • Oublier le contrôle d'accès. Une requête paramétrée qui renvoie la transaction d'un autre client reste une faille (A01).

Lab

Vous faites ce lab sur votre propre machine virtuelle de travail : lancez sudo ml demarrer devsecops-1-1, faites les tâches dans votre terminal, puis vérifiez avec sudo ml verifier.

Vous êtes chargé de la revue de sécurité d'un extrait de l'API d'AtlasPay, une fintech casablancaise : /root/atlaspay/src/paiements.js. Quatre défauts y sont marqués [1] à [4].

  1. Classez chaque défaut dans l'OWASP Top 10:2025 (fichier constats.txt).
  2. Corrigez l'injection et le contrôle d'accès avec une requête paramétrée filtrée sur le client connecté.
  3. Remplacez MD5 par scrypt avec un sel aléatoire.
  4. Faites en sorte que les erreurs ne révèlent plus rien au client.

Après chaque modification, contrôlez la syntaxe avec node --check /root/atlaspay/src/paiements.js et relisez le fichier avec cat -n.

Quiz

  1. Que signifie « shift-left » ?
  2. Dans quelle catégorie de l'OWASP Top 10:2025 classez-vous une API qui renvoie la commande d'un autre client quand on change l'identifiant dans l'URL ?
  3. Pourquoi une requête paramétrée protège-t-elle de l'injection SQL, alors que filtrer les apostrophes ne suffit pas ?
  4. Pourquoi MD5 est-il un mauvais choix pour stocker des mots de passe, et que faut-il utiliser ?
  5. Quelle catégorie est apparue dans le Top 10:2025 pour couvrir les bibliothèques, images et outils que l'on livre sans les avoir écrits ?
Réponses
  1. Déplacer les contrôles de sécurité vers le début du cycle de développement (conception, code, pull request) au lieu de les concentrer à la fin, là où les corrections coûtent le plus cher.
  2. A01, contrôle d'accès défaillant (cas typique d'IDOR).
  3. Parce que les valeurs sont transmises séparément du texte SQL : le moteur ne les interprète jamais comme du code. Un filtrage oublie toujours un cas (autre encodage, autre caractère, valeur numérique sans apostrophe).
  4. MD5 est rapide (des milliards d'essais par seconde) et, sans sel, deux mots de passe identiques donnent la même empreinte. Il faut une fonction lente et salée conçue pour cela : scrypt, Argon2id ou bcrypt.
  5. A03, défaillances de la chaîne d'approvisionnement logicielle (Software Supply Chain Failures).

Sources de référence : OWASP (CC BY-SA 4.0), documentation Node.js, CNDP. Texte original MasterLinux.

Exercices pratiques

À réaliser sur Node.js 22 (Debian, git, npm, python3)

Faites ce lab sur votre machine

Le terminal, c’est le vôtre : une VM Linux (VirtualBox, VMware, Hyper-V, WSL…) ou Docker/Podman. L’outil ml prépare l’exercice et corrige votre travail ; la leçon se valide toute seule ici.

Créez un compte gratuit pour obtenir votre commande personnelle et faire valider vos labs automatiquement.

Créer mon compte

Tâches à réaliser

  1. Lisez /root/atlaspay/src/paiements.js. Dans /root/atlaspay/constats.txt, écrivez la catégorie OWASP Top 10:2025 de chacun des quatre défauts marqués [1] à [4], une ligne par défaut au format numero=Axx (par exemple 1=A07).
    Afficher un indice[1] : on lit ce qui ne nous appartient pas. [2] : une donnée de l'utilisateur entre dans une requête SQL. [3] : un algorithme de hachage cassé. [4] : une erreur mal gérée qui bavarde. Écrivez par exemple : printf '%s\n' 1=A.. 2=A.. 3=A.. 4=A.. > /root/atlaspay/constats.txt
    Voir la solution
    printf '%s\n' 1=A01 2=A05 3=A04 4=A10 > /root/atlaspay/constats.txt

    Vérification : f=/root/atlaspay/constats.txt && grep -qx "1=A01" "$f" && grep -qx "2=A05" "$f" && grep -qx "3=A04" "$f" && grep -Eqx "4=A(10|02)" "$f"

  2. Corrigez voirTransaction : la requête doit être paramétrée (WHERE id = ? AND client_id = ?) avec les valeurs passées à part, dans un tableau [req.params.id, req.user.id]. Plus aucune interpolation ${req…} dans le SQL.
    Afficher un indiceRemplacez toute la ligne const lignes = … par : const lignes = await db.query("SELECT * FROM transactions WHERE id = ? AND client_id = ?", [req.params.id, req.user.id]); (sed -i 's|^ const lignes = .*|…|' fonctionne bien).
    Voir la solution
    sed -i 's|^  const lignes = .*|  const lignes = await db.query("SELECT * FROM transactions WHERE id = ? AND client_id = ?", [req.params.id, req.user.id]);|' /root/atlaspay/src/paiements.js

    Vérification : f=/root/atlaspay/src/paiements.js && ! grep -qF '${req' "$f" && grep -qF 'WHERE id = ? AND client_id = ?' "$f" && grep -qF 'req.user.id' "$f" && node --check "$f"

  3. Remplacez le hachage MD5 par crypto.scryptSync avec un sel aléatoire de 16 octets (crypto.randomBytes) : la fonction doit renvoyer le sel et l'empreinte en hexadécimal, séparés par « : ». Deux appels avec le même mot de passe doivent donc donner deux résultats différents.
    Afficher un indiceDans hacherMotDePasse : const sel = crypto.randomBytes(16); return sel.toString("hex") + ":" + crypto.scryptSync(mdp, sel, 64).toString("hex");
    Voir la solution
    sed -i 's|return crypto.createHash("md5").update(mdp).digest("hex");|const sel = crypto.randomBytes(16); return sel.toString("hex") + ":" + crypto.scryptSync(mdp, sel, 64).toString("hex");|' /root/atlaspay/src/paiements.js

    Vérification : f=/root/atlaspay/src/paiements.js && ! grep -qi 'createHash(.md5' "$f" && grep -q scryptSync "$f" && node -e 'const m=require("/root/atlaspay/src/paiements.js");const a=m.hacherMotDePasse("Rabat2026"),b=m.hacherMotDePasse("Rabat2026");process.exit(a!==b&&a.includes(":")&&a.length>100?0:1)'

  4. Le gestionnaire d'erreurs envoie le message et la pile d'appels au client. Journalisez l'erreur côté serveur (console.error(err)) et renvoyez seulement un message générique avec le code 500 : plus aucune propriété de err dans le json(...).
    Afficher un indiceUne seule ligne suffit : console.error(err); res.status(500).json({ erreur: "Erreur interne, réessayez plus tard" });
    Voir la solution
    sed -i 's|res.status(500).json({ erreur: err.message, pile: err.stack });|console.error(err); res.status(500).json({ erreur: "Erreur interne, reessayez plus tard" });|' /root/atlaspay/src/paiements.js

    Vérification : f=/root/atlaspay/src/paiements.js && ! grep -Eq 'json\(.*err\.' "$f" && grep -q 'status(500)' "$f" && grep -q 'console.error(err)' "$f" && node --check "$f"

Références officielles consultées : OWASP Top 10:2025 — https://owasp.org/Top10/2025/ ; OWASP DevSecOps Guideline — https://owasp.org/www-project-devsecops-guideline/ ; OWASP Cheat Sheet Series — Password Storage, SQL Injection Prevention, Error Handling — https://cheatsheetseries.owasp.org/ ; OWASP Application Security Verification Standard (ASVS) — https://owasp.org/www-project-application-security-verification-standard/ ; Node.js — documentation du module crypto (scrypt) — https://nodejs.org/api/crypto.html ; CNDP — Loi 09-08 relative à la protection des personnes physiques à l'égard du traitement des données à caractère personnel — https://www.cndp.ma/. Texte original MasterLinux.