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 :
jqpour 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é¶
- 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
- Lisez le
README.md, lepackage.jsonet leDockerfile, puis listez les routes exposées par l'API :
- 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 :
/healthet/api/infodansapp.js,/api/auth/logindansroutes/auth.js, et/api/payments/process,/calculate-taxet/recentdansroutes/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, leDockerfile, 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
- 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
- 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.jsonpour les dépendances directes,package-lock.jsonpour les versions réellement installées et les dépendances indirectes
- 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.jsexiste-t-elle vraiment ?
- La version est-elle fixée (
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
- 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.envlocal, le dossier.gitou 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 blocscatchdes routes
- 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 danspaymentService.js
- 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 :
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: Expressest présent, et aucun en-tête de sécurité (Content-Security-Policy,Strict-Transport-Security) n'est renvoyé /api/payments/recentrépond200sans jeton- Le jeton décodé contient
"role":"ADMIN": il a suffi de s'appeleradminet 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é¶
- Pour chaque constat, estimez la gravité avec la matrice, et justifiez-la en une phrase.
- Triez le tableau de la gravité la plus forte à la plus faible, et désignez les trois constats à corriger en premier.
- Comparez avec les cinq anomalies du README : lesquelles avez-vous retrouvées, et qu'avez-vous trouvé de plus ?
- 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.