Aller au contenu

Audit manuel d'un dépôt avec une grille d'analyse

Objectifs pédagogiques

À la fin de ce TP, vous devez être capables de :

  • parcourir un dépôt inconnu avec méthode, en suivant une grille d'analyse
  • repérer les failles les plus fréquentes : secret codé en dur, injection, contrôle d'accès absent, dépendance vulnérable, conteneur exécuté en root, configuration HTTP
  • décrire un constat de façon exploitable : emplacement, risque, gravité, piste de correction
  • estimer une gravité avec une méthode simple, et la justifier
  • mesurer le temps qu'un audit manuel demande, et ce qu'il apporte qu'un outil n'apporte pas

Prérequis

  • Git, un éditeur de code et un terminal (sous Windows : Git Bash ou WSL)
  • Un navigateur, pour consulter les bases de vulnérabilités
  • Aucune connaissance préalable en sécurité : le vocabulaire nécessaire est donné ci-dessous
  • Facultatif : jq pour lire les réponses JSON, et Node.js 20 ou plus récent pour l'atelier 3

Testé

Les commandes de recherche ont été jouées sur le dépôt SecurePay, et chaque constat du corrigé a été vérifié dans le code. Les preuves de l'atelier 3 ont été obtenues en lançant l'application en local. Les vulnérabilités des dépendances sont celles publiées dans la base OSV au 3 novembre 2026 : leur nombre augmente avec le temps.

Contexte

Vous rejoignez l'équipe de securepay-api, une API de paiement. Son README cite un audit externe qui a relevé cinq anomalies. Avant d'automatiser quoi que ce soit, vous menez votre propre audit, à la main : retrouver ces cinq anomalies, comprendre pourquoi ce sont des risques, et chercher ce que l'auditeur a manqué.

Le code contient des commentaires [AUDIT ANOMALY] laissés par cet auditeur : ce sont des indices, pas une liste complète, et l'un d'eux contient une erreur.

Notez l'heure de début. Le temps que vous passerez sur cet audit servira à justifier l'automatisation.

Le vocabulaire minimum

Terme Définition
Vulnérabilité Une faiblesse qu'un attaquant peut exploiter
Constat Une observation de l'audit : où, quoi, quel risque, quelle gravité
Secret Une donnée qui donne un accès : mot de passe, clé d'API, clé de signature d'un jeton
Jeton (JWT) Une preuve d'identité signée, remise après la connexion et renvoyée à chaque requête
CVE L'identifiant public d'une vulnérabilité connue, par exemple CVE-2022-23539
CVSS Un score de 0 à 10 qui mesure la sévérité d'une CVE, sans tenir compte de votre contexte
Gravité Ce que coûterait l'exploitation pour votre projet, compte tenu de son contexte
Faux positif Un constat qui, après analyse, n'est pas un risque réel

La méthode : estimer la gravité

Pour chaque constat, posez deux questions : qu'obtient l'attaquant (l'impact), et de quoi a-t-il besoin (la facilité) ?

Facilité ↓ / Impact → Faible : une information mineure Moyen : les données d'un utilisateur, un service dégradé Fort : prise de contrôle, argent, toutes les données
Facile : depuis Internet, sans compte Moyenne Haute Critique
Possible : avec un compte, ou sous condition Basse Moyenne Haute
Difficile : accès interne, plusieurs conditions Basse Basse Moyenne

Atelier 1 : Prendre en main le dépôt

Travail demandé

  1. Clonez votre copie du dépôt et repérez sa structure :
git clone https://github.com/<votre-compte>/securepay-api.git
cd securepay-api
git ls-files
git log --oneline
  1. Lisez le README.md, le package.json et le Dockerfile, puis listez les routes exposées par l'API :
grep -rnE "(router|app)\.(get|post|put|patch|delete)\(" src/
  1. Créez votre tableau des constats dans audit/constats.md :
N° Catégorie Emplacement Constat Risque : ce que peut faire un attaquant Gravité Piste de correction
C1 Secrets src/...:ligne ... ... ... ...

Ce que vous devez observer

  • Six routes : /health et /api/info dans app.js, /api/auth/login dans routes/auth.js, et /api/payments/process, /calculate-tax et /recent dans routes/payments.js
  • Le code métier est séparé dans src/services/paymentService.js
  • Quatre dépendances directes dans package.json, dont trois en version fixe

Critère de réussite

Vous savez dire ce que fait l'application, quelles routes elle expose et où se trouve chaque partie du code.

Atelier 2 : Parcourir la grille

Suivez la grille section par section. Pour chacune, lancez les commandes, lisez le code concerné, et ajoutez un constat dans votre tableau chaque fois que la réponse à une question vous inquiète.

A. Secrets

  • Ce que l'on cherche : des mots de passe, clés d'API ou clés de signature écrits dans le code, la configuration ou l'historique Git
  • Où regarder : src/, les fichiers .env*, la configuration, le Dockerfile, l'historique
grep -rniE "secret|password|api[_-]?key|token|sk_(test|live)_" \
  --exclude-dir=node_modules --exclude-dir=.git --exclude=package-lock.json .
git log -p | grep -niE "secret|password|sk_(test|live)_"
git log --all --diff-filter=D --name-only --format="%h %s"   # fichiers supprimés depuis
  • Questions à se poser :
    • Ce secret est-il réel, ou une valeur d'exemple ?
    • Que se passe-t-il si la variable d'environnement est absente au démarrage ?
    • Le secret figure-t-il dans l'historique, même s'il a été retiré depuis ?
Indice

La recherche remonte beaucoup de bruit : le README, les tests, les commentaires. Triez. Regardez de près les lignes qui contiennent || : c'est là que se cachent les valeurs par défaut.

B. Entrées utilisateur et injections

  • Ce que l'on cherche : une donnée envoyée par l'utilisateur qui arrive, sans contrôle, dans une fonction dangereuse
  • Où regarder : chaque route, de la lecture de la requête jusqu'à la réponse
grep -rnE "req\.(body|query|params)" src/
grep -rnE "eval\(|new Function|child_process|exec\(" src/
  • Questions à se poser :
    • Pour chaque donnée lue dans la requête, où va-t-elle ensuite ?
    • Est-elle validée : type, longueur, format, liste de valeurs autorisées ?
    • Peut-elle être exécutée comme du code, ou insérée dans une commande ou une requête ?
Indice

Suivez formula dans la route /calculate-tax, ligne par ligne.

C. Authentification et contrôle d'accès

  • Ce que l'on cherche : comment un utilisateur prouve son identité, et quelles routes le vérifient
  • Où regarder : la route de connexion, puis chaque route sensible
grep -rnE "jwt\.(sign|verify)" src/
grep -rn "password" src/
  • Questions à se poser :
    • Comment le mot de passe est-il vérifié ? Existe-t-il une base d'utilisateurs ?
    • Comment le rôle de l'utilisateur est-il attribué ?
    • Le jeton émis est-il vérifié ? Par quelles routes ?
    • Combien d'essais de mot de passe un attaquant peut-il faire ?
Indice

jwt.sign crée un jeton. Cherchez où il est contrôlé : combien de résultats pour jwt.verify ?

D. Dépendances

  • Ce que l'on cherche : des bibliothèques anciennes, touchées par des vulnérabilités publiques
  • Où regarder : package.json pour les dépendances directes, package-lock.json pour les versions réellement installées et les dépendances indirectes
cat package.json
grep -rnE "require\(['\"](axios|lodash|jsonwebtoken|express)" src/
  • Pour chaque dépendance : recherchez-la sur osv.dev ou dans la GitHub Advisory Database, et comparez la version utilisée aux versions corrigées. La même recherche en ligne de commande :
curl -s -X POST https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
  -d '{"package":{"name":"jsonwebtoken","ecosystem":"npm"},"version":"8.5.1"}' \
  | jq -r '.vulns[] | (.aliases // [] | join(" ")) + " " + .summary'
  • Questions à se poser :
    • La version est-elle fixée (8.5.1) ou en plage (^4.19.2) ? Qu'est-ce que cela change ?
    • Quelle est la dernière version, et quelles CVE corrige-t-elle ?
    • La bibliothèque est-elle seulement utilisée par le code ?
    • La CVE citée dans le commentaire de auth.js existe-t-elle vraiment ?

E. Conteneur

  • Ce que l'on cherche : une image trop grosse, un processus exécuté en root, des fichiers qui n'ont rien à faire dans l'image
  • Où regarder : le Dockerfile, et la présence d'un fichier .dockerignore
cat Dockerfile
ls -a
  • Questions à se poser :
    • Quel utilisateur exécute le processus dans le conteneur ?
    • L'image de base est-elle minimale ? Sa version est-elle précise ?
    • Que copie COPY . . ? Un fichier .env local, le dossier .git ou les tests finissent-ils dans l'image ?
    • Les outils de test (devDependencies) sont-ils installés dans l'image de production ?

F. Configuration HTTP et gestion des erreurs

  • Ce que l'on cherche : des en-têtes de sécurité absents, une bannière qui révèle la technologie, des messages d'erreur trop bavards
  • Où regarder : src/app.js, et les blocs catch des routes
grep -rn "helmet\|x-powered-by" src/
grep -rnE "(err|error)\.message" src/
  • Questions à se poser :
    • Que révèlent les en-têtes de réponse à un attaquant ?
    • Que lui apprend un message d'erreur renvoyé tel quel ?

G. Journalisation et caractère aléatoire

  • Ce que l'on cherche : l'absence de trace des événements de sécurité, et des identifiants prévisibles
  • Où regarder : tous les appels à console.log, et la création des références de transaction dans paymentService.js
grep -rn "console\.log" src/
grep -rn "Math.random" src/
  • Questions à se poser :
    • Une connexion échouée ou un paiement laissent-ils une trace exploitable ?
    • Une référence de transaction peut-elle être devinée ?

Critère de réussite

Les sept sections de la grille sont parcourues, et chaque constat est noté avec son emplacement exact.

Atelier 3 : Prouver par l'exécution (facultatif)

Un constat prouvé convainc plus qu'un constat supposé. Lancez l'application sur votre poste uniquement, puis vérifiez trois constats :

npm ci --omit=dev
npm start

Dans un second terminal :

# Les en-têtes de réponse
curl -i http://localhost:3000/health

# Les transactions, sans aucun jeton
curl -i http://localhost:3000/api/payments/recent

# Un jeton pour l'utilisateur « admin » : décodez sa partie centrale, sans l'envoyer sur un site tiers
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"password123"}' | jq -r .token)
echo "$TOKEN" | cut -d. -f2 | base64 -d   # la fin peut être tronquée : c'est un effet du format base64url

# Une « formule » qui n'est pas une formule
curl -s -X POST http://localhost:3000/api/payments/calculate-tax -H 'Content-Type: application/json' \
  -d '{"amount":100,"formula":"process.version"}'

Ce que vous devez observer

  • L'en-tête X-Powered-By: Express est présent, et aucun en-tête de sécurité (Content-Security-Policy, Strict-Transport-Security) n'est renvoyé
  • /api/payments/recent répond 200 sans jeton
  • Le jeton décodé contient "role":"ADMIN" : il a suffi de s'appeler admin et de connaître le mot de passe commun à tous
  • La « taxe » renvoyée est la version de Node.js du serveur : le texte envoyé a été exécuté. Un attaquant pourrait exécuter n'importe quelle commande à la place

Uniquement sur votre poste

Ces vérifications se font sur une application que vous lancez vous-même. Tester une application sans l'autorisation écrite de son propriétaire est illégal.

Atelier 4 : Qualifier, prioriser et comparer

Travail demandé

  1. Pour chaque constat, estimez la gravité avec la matrice, et justifiez-la en une phrase.
  2. Triez le tableau de la gravité la plus forte à la plus faible, et désignez les trois constats à corriger en premier.
  3. Comparez avec les cinq anomalies du README : lesquelles avez-vous retrouvées, et qu'avez-vous trouvé de plus ?
  4. Notez le temps passé, au total et par section de la grille.

Critère de réussite

Au moins cinq constats correctement qualifiés, dont les cinq anomalies du README, et un classement justifié par la matrice.

Niveau avancé

Ajoutez deux colonnes à votre tableau :

  • Catégorie OWASP Top 10 2025 : la famille de risque à laquelle se rattache le constat
Code Catégorie
A01 Broken Access Control : contrôle d'accès défaillant
A02 Security Misconfiguration : configuration de sécurité défaillante
A03 Software Supply Chain Failures : chaîne d'approvisionnement logicielle
A04 Cryptographic Failures : défaillances cryptographiques
A05 Injection
A06 Insecure Design : conception non sécurisée
A07 Authentication Failures : défaillances d'authentification
A08 Software or Data Integrity Failures : intégrité du logiciel ou des données
A09 Security Logging and Alerting Failures : journalisation et alertes
A10 Mishandling of Exceptional Conditions : mauvaise gestion des cas d'erreur
  • Étape où le constat aurait dû être arrêté : poste du développeur (hook pre-commit, extension de l'éditeur), merge request (revue, analyse automatique), construction de l'image, ou déploiement

Questions de synthèse

  • Combien de temps a pris votre audit ? Combien de temps faudrait-il pour le refaire à chaque modification du code ?
  • Quels constats un outil automatique trouvera-t-il facilement ? Lesquels exigent de comprendre l'application ?
  • Pourquoi la gravité d'un constat dépend-elle du contexte, alors que le score CVSS d'une CVE n'en dépend pas ?
  • Pourquoi supprimer un secret du code ne suffit-il pas à régler le problème ?
  • Pourquoi un commentaire de code ou un rapport d'audit doivent-ils être vérifiés à la source ?

Corrigé

Corrigé : les constats attendus

Les anomalies du README sont marquées d'une étoile (★) : la quatrième, sur les dépendances, couvre C6 et C7.

N° Catégorie Emplacement Constat Gravité Piste de correction OWASP 2025
C1 Injection routes/payments.js:62 ★ eval() exécute la formula envoyée par l'utilisateur : exécution de code à distance, sans compte Critique Supprimer eval() ; n'accepter qu'un taux numérique validé A05
C2 Accès routes/payments.js (3 routes) Le jeton émis par /login n'est jamais vérifié : paiements et transactions accessibles sans authentification Critique Middleware qui vérifie le jeton (jwt.verify) sur toutes les routes /api/payments A01
C3 Accès routes/auth.js:21-24 Mot de passe unique password123 pour tous, et rôle ADMIN accordé à quiconque s'appelle admin Critique Base d'utilisateurs, mots de passe hachés (bcrypt, Argon2), rôles stockés côté serveur A07, A01
C4 Secrets routes/payments.js:11 et .env.example:12 ★ Clé Stripe codée en dur, et présente dans l'historique Git. C'est une clé de test (sk_test_) : pas d'argent réel en jeu, mais le même défaut avec une clé de production serait critique Haute Révoquer la clé, puis la retirer du code ; l'injecter par une variable masquée ou un gestionnaire de secrets A07
C5 Secrets routes/auth.js:7 Clé de signature des jetons avec une valeur par défaut : si la variable manque, n'importe qui peut fabriquer un jeton valide Haute Refuser de démarrer sans JWT_SECRET A04
C6 Dépendances package.json ★ jsonwebtoken 8.5.1 est touché par trois CVE (CVE-2022-23539, CVE-2022-23540, CVE-2022-23541). Elles visent surtout la vérification des jetons, absente aujourd'hui : elles deviendront exploitables dès que C2 sera corrigé avec cette version Haute Passer à jsonwebtoken 9.x avant de corriger C2 ; vérifier dans OSV que la nouvelle version n'est plus touchée A03
C7 Dépendances package.json ★ axios 0.21.1 et lodash 4.17.20 sont touchés par des CVE de sévérité haute, mais ne sont utilisés nulle part dans src/ : leurs failles ne sont pas atteignables par le code, elles alourdissent seulement l'image et les rapports Basse Les retirer A03
C8 Conteneur Dockerfile ★ Processus exécuté en root, image node:20 complète de plus de 1 Go Haute Build multi-stage, image minimale, USER non root A02
C9 Conteneur Dockerfile:19-22 Pas de .dockerignore : COPY . . embarque un éventuel .env local, .git et les tests ; npm install installe aussi les outils de test Moyenne .dockerignore, npm ci --omit=dev A02
C10 Authentification routes/auth.js:13 Aucune limite d'essais sur la connexion : force brute possible Moyenne Limitation du nombre de requêtes, verrouillage temporaire A07
C11 Configuration HTTP app.js ★ Aucun en-tête de sécurité, bannière X-Powered-By: Express. Ces absences ne sont exploitables qu'en complément d'une autre faille Basse helmet, app.disable('x-powered-by') A02
C12 Erreurs routes/payments.js:40 et :70 Le message d'erreur interne est renvoyé tel quel au client : il aide un attaquant à préparer une autre attaque Basse Message générique au client, détail dans les journaux A10
C13 Journalisation src/ Aucune trace des connexions échouées ni des paiements : une attaque passerait inaperçue. La matrice ne s'applique pas directement : ce manque aggrave toutes les autres failles Moyenne Journaux structurés des événements de sécurité, sans données sensibles A09
C14 Aléa services/paymentService.js:65 Référence de transaction construite avec Math.random(), qui n'est pas cryptographiquement sûr Basse crypto.randomUUID() A04

Le commentaire erroné : auth.js cite la CVE-2022-23529. Cette CVE a été retirée en janvier 2023 : elle supposait que l'attaquant contrôle déjà la clé passée à la bibliothèque. Les vraies CVE de jsonwebtoken 8.5.1 sont la CVE-2022-23539, la CVE-2022-23540 et la CVE-2022-23541. Un commentaire ou un rapport se vérifie toujours à la source.

Les trois priorités : C1, C2 et C3, exploitables depuis Internet sans aucun compte. La révocation de la clé de C4 est immédiate, même si sa correction complète prend plus de temps.

Ce que l'auditeur a manqué : parmi les trois constats critiques, seul C1 figure dans le README. C2, le jeton jamais vérifié, et C3, le mot de passe commun, sont des failles de conception : aucun outil d'analyse ne les signale avec ses règles par défaut. C5, C9, C10, C12 et C13 sont absents du README, tout comme C14, que seul un commentaire du code mentionne.