Trouver & gérer les vulnérabilités
Le travail continu de sécurité : revue de code, découverte des failles (tests, bug bounty) et gestion/priorisation des vulnérabilités.
La sécurité n'est pas un état que l'on atteint une fois pour toutes, c'est un processus continu. Même une application bien architecturée et soigneusement relue finit par héberger une faille : une revue qui laisse passer un cas, un environnement de production qui se met à jour, une dépendance qui change de comportement. Le bon réflexe n'est donc pas de viser le « zéro vulnérabilité », illusoire, mais de mettre en place une boucle : on cherche, on trie, on corrige, on vérifie, puis on recommence.
Ce chapitre suit cette boucle dans l'ordre où Hoffman la déroule. D'abord la revue de code orientée sécurité, qui traque les failles avant qu'elles n'atteignent la production. Ensuite la découverte des vulnérabilités qui passent quand même : automatisation, tests, et chercheurs externes. Enfin la gestion : reproduire, scorer, prioriser et boucler le cycle. Un rappel d'emblée, qui vaut pour tout le chapitre : toute recherche de vulnérabilités doit être encadrée et autorisée. On teste ce que l'on possède, ou ce pour quoi on a une permission explicite et écrite — jamais les systèmes des autres.
La revue de code orientée sécurité
La revue de code de sécurité (security code review) ressemble beaucoup à une revue de code fonctionnelle classique, ce qui en raccourcit l'apprentissage. La différence n'est pas l'outil mais l'intention : là où la revue fonctionnelle vérifie qu'une fonctionnalité respecte sa spécification, la revue de sécurité traque les vulnérabilités.
Hoffman insiste sur le moment : la revue de code doit toujours venir après la revue d'architecture, jamais avant. L'architecture détecte des failles hypothétiques ; le code est l'endroit où on les trouve réellement. En pratique, le meilleur point d'ancrage est la requête de fusion (merge request, souvent appelée pull request) : c'est l'instant où l'ensemble de la fonctionnalité est développé et intégré, et où l'on peut donc l'examiner d'un seul tenant.
Astuce
Une revue locale est plus puissante qu'une revue dans l'éditeur web de GitHub
ou GitLab. Récupérez la branche, puis comparez-la à la branche principale avec git diff origin/master... pour obtenir la liste des fichiers et des
changements à inspecter. C'est le point de départ commun aux revues
fonctionnelles et de sécurité.
Failles archétypales contre bugs de logique
Une revue de sécurité cherche deux catégories de problèmes. Les premières sont les vulnérabilités archétypales (archetypical vulnerabilities) : XSS, CSRF, injection — les classes bien connues, que les outils automatiques savent souvent repérer. Les secondes sont les bugs de logique (logic-level vulnerabilities), spécifiques à votre application, qu'aucun scanner ne trouvera car ils exigent de comprendre l'intention de la fonctionnalité : qui sont les utilisateurs, ce qu'ils peuvent faire, et quel est l'impact métier.
Hoffman illustre avec une plateforme à trois rôles — user, member, moderator — où seuls les membres peuvent publier des vidéos. Une faille archétypale serait un XSS dans un message. Une faille de logique serait un point d'API mal codé qui accepte une charge utile contenant isMember: true, permettant à un simple utilisateur de s'octroyer des droits qu'un modérateur ne lui a jamais accordés.
POST /api/posts HTTP/1.1
Content-Type: application/json
{ "type": "video", "url": "...", "isMember": true } Le serveur ne doit jamais déduire le rôle d'un champ envoyé par le client. Le niveau d'autorisation se lit côté serveur, à partir de la session ou du jeton authentifié, jamais à partir du corps de la requête.
Par où commencer
Idéalement, on commence par les composants à plus haut risque. Mais sur une application que l'on n'a pas conçue — cas fréquent en conseil ou sur l'existant —, on ignore où ils sont. Hoffman propose alors un parcours organique qui suit le flux des données du client vers le serveur :
- Lire le code client pour comprendre la logique métier et ce que les utilisateurs peuvent faire.
- Suivre les appels d'API découverts côté client, et identifier les dépendances de la couche API.
- Tracer ces dépendances : bases de données, bibliothèques d'aide, fonctions de journalisation, fichiers téléversés.
- Chercher les API exposées involontairement ou prévues pour de futures fonctionnalités, et les relire.
- Couvrir le reste du code, désormais facile à parcourir puisque l'application est devenue familière.
Ce chemin donne une connaissance progressive de l'application et permet de prioriser les surfaces exposées aux utilisateurs, en laissant le code à faible risque pour la fin.
Les anti-patterns à traquer
Au-delà des failles connues, certaines tournures « ressemblent à des solutions » mais deviennent des problèmes en production. Les repérer accélère considérablement la revue.
Les listes noires (blacklists). Une liste noire ne protège que si l'on a une connaissance parfaite de toutes les entrées présentes et futures — ce qui n'arrive jamais. Préférez toujours une liste blanche (whitelist), qui n'autorise que ce qui est explicitement validé.
// ❌ Avant : liste noire, contournable en achetant un autre domaine.
const blacklist = ['http://www.evil.com', 'http://www.badguys.net'];
const isDomainAccepted = function (domain) {
return !blacklist.includes(domain);
}; // ✅ Après : liste blanche, seul l'explicitement autorisé passe.
const whitelist = ['https://happy-site.com', 'https://my-friends.com'];
const isDomainAccepted = function (domain) {
return whitelist.includes(domain);
}; L'objection habituelle — « une liste blanche demande de la maintenance » — se résout par une combinaison d'effort manuel et automatisé : exiger qu'un partenaire soumette son site et ses justificatifs avant ajout rend une intégration malveillante très difficile à faire passer.
Le code générique (boilerplate). Beaucoup de frameworks livrent une configuration peu sûre par défaut et exigent qu'on la resserre plutôt que l'inverse. L'exemple classique reste les anciennes versions de MongoDB, accessibles depuis Internet sans authentification, qui ont vu des dizaines de milliers de bases détournées contre rançon — alors que quelques lignes de configuration suffisaient à les fermer. Une page 404 par défaut peut aussi trahir la version exacte de votre framework. Ne mettez jamais en production du code générique non évalué.
La confiance par défaut (trust-by-default). Si une application tourne sous un compte unique disposant à la fois des droits de journalisation, d'écriture disque et de base de données, alors une seule faille compromet les trois. Donnez à chaque module ses propres permissions, strictement limitées à ce dont il a besoin : une faille dans le module SQL ne doit pas ouvrir l'accès aux fichiers ou aux journaux.
Le couplage client/serveur. Quand le code client et serveur sont si liés qu'ils ne peuvent fonctionner l'un sans l'autre (logique d'authentification mêlée à du templating PHP, par exemple), le serveur se retrouve à parser du HTML et à se prémunir contre l'altération de paramètres dans plusieurs formats. Une application correctement séparée fait communiquer client et serveur via un format de données prédéfini : le serveur rejette tout HTML et n'accepte que des charges utiles au format attendu. La séparation des préoccupations simplifie les mécanismes de sécurité.
Piège courant
Une atténuation temporaire ne devrait être déployée que si une vraie solution permanente est déjà planifiée avec une échéance. Une liste noire, un correctif partiel ou un contournement « provisoire » qui s'installe durablement finit presque toujours par être contourné.
Outiller la revue : SAST et linters
La relecture humaine ne passe pas à l'échelle seule. On l'épaule avec de l'analyse statique (static analysis, SAST) : des scripts qui lisent le code source — sans l'exécuter — pour y repérer erreurs de syntaxe et fautes courantes. Un linter de sécurité tourne localement pendant le développement ; les mêmes outils s'exécutent à la demande sur le dépôt ou à chaque commit dans l'intégration continue (CI). Hoffman cite Checkmarx (la plupart des langages, payant), PMD (Java), Bandit (Python) et Brakeman (Ruby).
Configurez ces outils pour viser les vulnérabilités du Top 10 OWASP. Quelques exemples de motifs détectables statiquement :
| Faille | Motif recherché par l'analyse statique |
|---|---|
| XSS général | Manipulation du DOM via innerHTML |
| XSS réfléchi | Variable tirée d'un paramètre d'URL |
| XSS du DOM | Puits DOM dangereux, ex. setInterval() |
| Injection SQL | Chaîne fournie par l'utilisateur dans une requête |
| CSRF | Requête GET qui change l'état du serveur |
| Déni de service | Expression régulière mal écrite |
On peut aller plus loin et imposer des règles de codage : rejeter une API qui n'importe pas la fonction d'autorisation attendue, ou une fonction consommant une entrée utilisateur sans passer par la bibliothèque de validation unique (single source of truth).
Note
L'analyse statique brille sur les langages typés statiquement (Java, C#), où l'outil connaît le type attendu. Elle peine sur les langages dynamiques comme JavaScript, où les variables sont mutables et changent à l'exécution. Attendez-vous aussi à de nombreux faux positifs : ils sont le prix d'une détection à large spectre, et non un défaut à éliminer à tout prix.
Découvrir les vulnérabilités résiduelles
Une application bien architecturée connaît le moins de failles ; une bonne revue de code en retire encore ; mais il en passe toujours. Il faut donc des processus de découverte qui ciblent le code en production, pas seulement le code en amont.
L'automatisation : statique, dynamique, régression
L'automatisation n'attrape pas tout, mais elle est bon marché, efficace et durable. Elle excelle sur les failles routinières et échoue sur les vulnérabilités de logique propres à votre application ou celles qui résultent d'un enchaînement (chaining) — plusieurs faiblesses mineures combinées en une attaque sérieuse. Elle prend trois formes complémentaires.
| Type | Quand | Forces | Limites |
|---|---|---|---|
| Statique (SAST) | Avant exécution, en CI | Bon marché, large couverture | Faux positifs ; faible sur langages dynamiques |
| Dynamique (DAST) | Après exécution | Confirme de vraies failles, peu de faux positifs | Coûteuse, lente, exige un environnement type prod |
| Tests de régression | À chaque commit/push | Simples, empêchent la réapparition | Ne couvrent que les failles déjà connues |
L'analyse dynamique (dynamic analysis, DAST) exécute le code puis compare ses sorties à un modèle décrivant vulnérabilités et mauvaises configurations. Elle voit ce que l'analyse statique ne fait que deviner, ce qui la rend précieuse sur les langages dynamiques et sur les failles qui surgissent comme effet de bord d'un fonctionnement normal — donnée sensible mal stockée en mémoire, attaques par canal auxiliaire. Son coût est sa rançon : il lui faut un environnement proche de la production (serveurs, licences). Hoffman cite IBM AppScan et Veracode (payants) et Iroh (gratuit).
Les tests de régression de vulnérabilité (vulnerability regression testing) sont les plus simples et souvent les plus rentables. Ils fonctionnent comme des tests fonctionnels, mais re-testent des failles déjà corrigées pour s'assurer qu'un retour arrière ou une réécriture ne les réintroduit pas. Aucun framework spécial n'est requis. Hoffman illustre avec une faille CSRF sur un point changeSubscriptionTier qui acceptait un GET modifiant l'état ; le correctif consiste à n'accepter que POST, et le test de régression vérifie que seul ce verbe est autorisé (dans cet exemple précis ; une défense CSRF complète exige en plus un jeton anti-CSRF et/ou des cookies SameSite).
// Échoue si l'endpoint accepte plus d'un verbe, ou autre chose que POST.
const testTierChange = function () {
requester
.options('http://app.com/api/changeSubscriptionTier')
.on('response', function (res) {
const allow = res.headers && res.headers['Allow'];
if (!allow) return tester.fail();
const verbs = allow.split(',');
if (verbs.length > 1) return tester.fail();
if (verbs[0] !== 'POST') return tester.fail();
});
}; Ces tests gagnent en valeur avec le temps : plus une base de code grandit, plus les occasions de régression se multiplient. Exécutez-les sur les hooks de commit ou de push (rejet en cas d'échec), à défaut sur une planification quotidienne.
Tests, fuzzing et pentests autorisés
Au-delà de l'automatisation continue, plusieurs techniques actives complètent la découverte.
| Technique | Principe | Place dans le cycle |
|---|---|---|
| Tests de sécurité | Cas de test ciblant des comportements à risque | Continu, en CI |
| Analyse dynamique (DAST) | Exécuter puis comparer à un modèle de failles | Sur env. type prod |
| Fuzzing | Envoyer des entrées malformées pour provoquer un comportement anormal | Ciblé, sur parseurs/entrées |
| Pentest autorisé | Test d'intrusion mandaté par contrat | Avant et après mise en prod |
| Bug bounty | Chercheurs externes incités, cadre défini | En continu, post-prod |
Le fuzzing consiste à bombarder une entrée de données aléatoires ou malformées pour déclencher un plantage, une erreur révélatrice ou un état inattendu — particulièrement utile sur les analyseurs de format et les frontières d'entrée. Conceptuellement, c'est une recherche automatisée de cas non gérés ; appliquez-le uniquement à vos propres systèmes ou dans un périmètre de test autorisé.
Le test d'intrusion par un tiers (third-party penetration testing) apporte un regard que votre équipe ne peut pas avoir sur son propre code. Contrairement aux chasseurs de bugs, une société de pentest peut se voir assigner des zones précises et, sous accord légal, recevoir le code source pour des résultats plus fins. Visez en priorité les zones à haut risque et le code récemment écrit, avant la mise en production ; les tests post-production restent utiles pour vérifier la constance des mécanismes de sécurité.
Attention
Fuzzing, pentest et toute forme de test offensif ne sont légitimes que dans un périmètre autorisé. Le droit distingue mal la recherche « chapeau blanc » de l'exploitation malveillante : sans permission écrite, même un test bien intentionné contre un système tiers peut tomber sous le coup de la loi. Ne testez jamais ce que vous ne possédez pas sans mandat explicite.
Divulgation responsable et bug bounty
Vos tests internes ne couvriront jamais tous les usages de vos clients. Certains utilisateurs trouveront des failles — mais ne les signaleront pas si le faire les expose à un risque juridique. Plusieurs grandes organisations ont transformé des signalements en poursuites, ce qui dissuade durablement les utilisateurs techniques de rapporter quoi que ce soit.
La parade est un programme de divulgation responsable (responsible disclosure) clair et public. Il précise comment tester l'application sans risque légal, une méthode de soumission, un modèle de rapport, et souvent une clause de confidentialité temporaire : le chercheur s'engage à ne pas divulguer publiquement la faille pendant la période (semaines ou mois) nécessaire à sa correction. C'est l'essence de la divulgation coordonnée : laisser le temps de corriger avant toute publication.
La divulgation responsable autorise le signalement mais ne récompense pas la recherche. Le bug bounty comble ce manque en offrant des primes en échange de rapports correctement documentés. Des plateformes comme HackerOne ou BugCrowd fournissent modèles légaux, interface de soumission et triage, abaissant la barrière d'entrée pour les petites équipes. Combiner une politique de divulgation responsable et un programme de bug bounty incite des testeurs externes à la fois à trouver et à rapporter les failles.
Gérer les vulnérabilités : la boucle complète
Une faille a été découverte, en interne ou par un tiers. Commence alors le pipeline de gestion des vulnérabilités, qui suit quatre temps : reproduire → trier (reproduction + scoring) → remédier → vérifier.
Reproduire avant tout
La première étape est de reproduire la faille dans un environnement proche de la production. C'est ce qui distingue une vraie vulnérabilité d'une fausse alerte — par exemple un utilisateur qui a « accidentellement » rendu une photo publique alors qu'il la croit privée. Pour cela, montez un environnement de pré-production (staging) qui imite la production au plus près, avec des utilisateurs et objets factices, et automatisez entièrement sa mise en place.
Reproduire évite de gaspiller des heures d'ingénierie sur des faux positifs — et, pour un bug bounty, évite de payer une prime pour une faille inexistante. La reproduction donne aussi une compréhension profonde de la cause, indispensable à la correction. Reproduisez immédiatement et journalisez le résultat.
Scorer la gravité avec le CVSS
Une fois la faille reproduite, on en comprend le mécanisme de livraison et le type de risque encouru. Il faut alors la scorer avec un système robuste mais flexible. Le plus répandu est le CVSS (Common Vulnerability Scoring System), librement publié et bien documenté — un excellent point de départ pour les organisations à budget limité. Il se décompose en trois volets :
- Base — la gravité de la faille en elle-même, dans le vide.
- Temporel (temporal) — l'état des atténuations et la qualité du rapport au moment du signalement.
- Environnemental (environmental) — la gravité relative à votre contexte applicatif.
Le score de base prend huit entrées qui décrivent à la fois l'accessibilité de l'attaque et son impact.
| Entrée de base | Ce qu'elle mesure |
|---|---|
| Vecteur d'attaque (AV) | Réseau, adjacent, local ou physique — réseau étant le plus grave |
| Complexité (AC) | Difficulté d'exploitation : « basse » (rejouable) ou « haute » (conditions précises) |
| Privilèges requis (PR) | Aucun (invité), bas (utilisateur) ou haut (admin) |
| Interaction utilisateur (UI) | Aucune, ou requise (cliquer un lien) |
| Portée (S) | Inchangée (système local) ou changée (déborde vers l'OS, le système de fichiers) |
| Confidentialité (C) | Aucune / basse / haute, selon les données exposées |
| Intégrité (I) | Aucune / basse / haute, selon l'état applicatif modifiable |
| Disponibilité (A) | Aucune / basse / haute, impact sur l'accès des utilisateurs légitimes |
Le résultat est un nombre de 0 à 10, traduisible en paliers : 0,1–4 faible, 4,1–6,9 moyen, 7–8,9 élevé, 9+ critique. Le score temporel affine selon l'exploitabilité (de « non prouvé » à « élevé »), le niveau de remédiation disponible (d'« aucun correctif » à « correctif officiel ») et la confiance dans le rapport. Le score environnemental reprend les entrées de base en y ajoutant l'importance — pour votre organisation — de la confidentialité, de l'intégrité et de la disponibilité : une application de santé ou gouvernementale score plus haut qu'un bac à sable jetable.
À retenir
Le CVSS est généraliste : il est critiqué pour mal noter les failles rares, uniques ou enchaînées, et les systèmes atypiques. Si votre application pilote du matériel physique (caméra de sécurité, IoT), développez votre propre système de scoring dès le départ : une caméra compromise peut divulguer des images de ses occupants — avec des implications légales que le CVSS ne capte pas.
Prioriser, remédier, vérifier
Le score est un point de départ de la priorisation, pas son seul critère. Croisez-le avec l'exploitabilité réelle, l'exposition et la criticité de l'actif, ainsi que des facteurs métier comme les contrats clients et les relations commerciales.
| Critère de priorisation | Question à poser |
|---|---|
| Gravité (CVSS) | Quel est le score de base ? Faible, moyen, élevé, critique ? |
| Exploitabilité réelle | Existe-t-il une preuve de concept fonctionnelle, ou une théorie ? |
| Exposition | La surface est-elle publique, ou interne/derrière authentification ? |
| Criticité de l'actif | Quelles données ou opérations sont en jeu pour l'organisation ? |
| Contraintes métier | Des contrats ou engagements clients imposent-ils une échéance ? |
Vient ensuite la remédiation. Corriger correctement importe autant que trouver. Privilégiez toujours une solution permanente et applicable à toute la surface de l'application, bien testée pour couvrir les cas limites. Quand c'est impossible, déployez un correctif temporaire — mais ouvrez aussitôt un nouveau ticket décrivant la surface encore vulnérable, avec son propre score.
Piège courant
Ne fermez jamais un ticket de sécurité avec un correctif partiel sans avoir d'abord ouvert un ticket documentant le reste à corriger. Fermer trop tôt fait perdre des heures de reproduction et de compréhension technique. De plus, toutes les failles ne sont pas signalées, et le risque d'une faille grandit à mesure que la surface exposée de l'application augmente.
Enfin, la vérification : chaque ticket de sécurité fermé doit être livré avec un test de régression. Ces tests prennent une valeur croissante avec le temps, car les occasions de régression augmentent de façon exponentielle avec la taille et le nombre de fonctionnalités. C'est ce qui ferme la boucle et empêche une faille corrigée de revenir par une réécriture ou un retour arrière.
Une culture où la sécurité est l'affaire de tous
Ce pipeline n'est efficace que s'il s'intègre dans un cycle de développement logiciel sécurisé (secure software development life cycle, SSDL) et dans une culture partagée. La revue de sécurité devrait être menée par des ingénieurs au fait de la sécurité aux côtés du développeur de la fonctionnalité, et non par une équipe isolée qui arrive après coup. Une revue extérieure offre par ailleurs un regard neuf qui repère aussi des bugs et défauts d'architecture ordinaires — bénéfice pour la qualité autant que pour la sécurité.
C'est l'idée d'une dette de sécurité que l'on gère comme la dette technique : on l'inventorie, on la priorise, on la rembourse au fil de l'eau, et on évite d'en accumuler en intégrant la sécurité à chaque étape plutôt qu'en fin de course.
À retenir
- La sécurité est une boucle continue : découverte → triage → remédiation → vérification, à répéter — pas un état figé que l'on atteint une fois.
- La revue de code de sécurité vient après l'architecture, idéalement à la fusion : elle traque les failles archétypales (XSS, CSRF, injection) et les bugs de logique propres à l'application, en se méfiant des anti-patterns (listes noires, code générique, confiance par défaut, couplage client/serveur).
- Automatisez la découverte : SAST en CI pour le large spectre, DAST pour confirmer les vraies failles, tests de régression pour empêcher les retours en arrière ; complétez par fuzzing et pentests.
- N'attaquez jamais sans autorisation : fuzzing, pentests et bug bounty exigent un périmètre défini et une permission écrite ; favorisez la divulgation responsable et coordonnée.
- Triez avec méthode : reproduisez d'abord, scorez avec le CVSS (base, temporel, environnemental), puis priorisez en croisant gravité, exploitabilité réelle, exposition et criticité de l'actif.
- Corrigez durablement et vérifiez : préférez les correctifs permanents, ouvrez un ticket pour toute surface restée vulnérable, et livrez un test de régression avec chaque faille fermée — dans une culture où la sécurité est l'affaire de toute l'équipe.