Sécurité et identité

OWASP Top 10 : les dix catégories de risques et comment corriger chacune

Intermédiaire10 min de lectureMis à jour

Cette page a été traduite de l'anglais avec l'aide de l'IA, puis vérifiée par rapport à l'original. Lire la version anglaise

L'OWASP Top 10 est un document de sensibilisation qui recense les catégories de risques à l'origine de la plupart des compromissions d'applications web. Ce guide passe en revue chaque catégorie avec un exemple réaliste de la faille et la correction précise dans une stack Java et web, puis explique comment utiliser la liste comme point de départ plutôt que comme checklist, et à quel moment l'Application Security Verification Standard prend le relais.

Ce que vous allez apprendre

  • Reconnaître chaque catégorie de l'OWASP Top 10 dans du code applicatif réel
  • Appliquer la mesure concrète contre le contrôle d'accès défaillant, l'injection et les défaillances cryptographiques
  • Distinguer une conception non sécurisée d'un bug d'implémentation et traiter chacun de manière adaptée
  • Maîtriser le risque lié aux dépendances et à la supply chain grâce à un SBOM et à des builds reproductibles
  • Utiliser l'ASVS pour transformer la sensibilisation en exigences de sécurité testables

L'OWASP Top 10 est le document le plus cité en sécurité applicative, et aussi le plus mal utilisé. Il s'agit d'un classement périodiquement révisé et fondé sur des données, qui recense les catégories de faiblesses les plus fréquentes dans les applications réelles — un moyen d'orienter l'attention, écrit pour sensibiliser et non pour certifier. Lu de cette façon, il a une vraie valeur : les catégories sont assez larges pour survivre aux modes des frameworks, et chacune désigne une classe d'erreurs qui a coûté très cher aux organisations.

Vous trouverez ci-dessous chaque catégorie, avec une faille concrète du type de celles que l'on rencontre dans du code ordinaire et le contrôle précis qui l'élimine. L'angle est toujours défensif : juste ce qu'il faut sur la manière dont la faiblesse est exploitée pour la repérer en revue de code, accompagné de la correction. Lorsqu'un sujet mérite plus qu'un résumé, un lien renvoie vers la suite — la vue architecturale se trouve dans architecture de sécurité applicative, et la gestion des jetons propre aux API dans sécuriser des API REST avec JWT.

Contrôle d'accès défaillant

L'éternel numéro un : un utilisateur authentifié qui accède à des données ou à des opérations appartenant à quelqu'un d'autre. Le cas d'école est un identifiant repris tel quel de la requête pour charger un enregistrement, sans vérifier que cet enregistrement appartient bien à l'appelant.

// Vulnérable : tout utilisateur authentifié peut lire n'importe quelle facture en changeant le numéro
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable long id) {
    return invoices.findById(id).orElseThrow(NotFoundException::new);
}

// Corrigé : la requête est restreinte au principal authentifié
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable long id, Authentication auth) {
    return invoices.findByIdAndCustomer(id, auth.getName())
                   .orElseThrow(NotFoundException::new);   // 404 plutôt que 403 : ne rien révéler
}

Variantes voisines : l'élévation de privilèges via une route d'administration non protégée, un identifiant de rôle ou de tenant accepté depuis un paramètre de requête, la navigation forcée vers un endpoint que l'interface ne propose jamais, et le directory traversal dans un téléchargement de fichier. Les contrôles sont structurels — refuser par défaut pour qu'une nouvelle route soit protégée tant qu'on ne l'a pas ouverte, restreindre chaque requête au principal, déduire le tenant du jeton plutôt que du payload, et écrire pour chaque endpoint un test qui vérifie que l'identifiant d'un autre utilisateur renvoie 404. Les règles au niveau des méthodes du service, présentées dans les bases de Spring Security, rattrapent ce que les motifs d'URL laissent passer.

Défaillances cryptographiques

Cette catégorie concerne les données sensibles qui ne sont pas protégées comme elles devraient l'être : transmises en clair, stockées sans chiffrement, ou protégées par quelque chose qui ressemble seulement à de la cryptographie. Les constats typiques : un service interne qui accepte encore du HTTP en clair, des sauvegardes de base de données écrites sans chiffrement sur un stockage partagé, des données de carte bancaire ou de santé conservées bien après qu'elles ont cessé d'être nécessaires, des mots de passe stockés avec une fonction de hachage rapide, et un « chiffrement » maison à base de XOR ou de base64.

Pour corriger, commencez par classifier les données — on ne protège pas ce qu'on n'a pas identifié —, puis exigez TLS 1.3 partout, y compris à l'intérieur du réseau, chiffrez au repos avec des clés conservées dans un service de gestion de clés managé, hachez les mots de passe avec une fonction memory-hard, et utilisez un chiffrement authentifié comme AES-GCM avec un nonce unique par message plutôt que des algorithmes de chiffrement par blocs utilisés tels quels. Supprimez ce dont vous n'avez plus besoin : une donnée que vous ne détenez pas ne peut pas fuiter. Les primitives elles-mêmes sont expliquées dans les fondamentaux de la sécurité de l'information.

Injection

Il y a injection chaque fois qu'une entrée non fiable est concaténée dans quelque chose qu'un interpréteur analyse — SQL, commande système, filtres LDAP, XPath, expressions de template. Le cas de la base de données reste le plus dévastateur.

// Vulnérable : l'entrée fait partie de l'instruction
String sql = "SELECT * FROM users WHERE email = '" + email + "'";

// Corrigé : la valeur ne peut jamais modifier la structure de l'instruction
String sql = "SELECT id, email, display_name FROM users WHERE email = ?";
jdbcTemplate.query(sql, ps -> ps.setString(1, email), USER_MAPPER);

Les requêtes paramétrées sont la réponse partout où l'interpréteur les prend en charge ; les pratiques qui les entourent sont traitées dans les bonnes pratiques SQL. Lorsque la structure varie réellement — une colonne de tri, un nom de table —, validez contre une allow-list d'identifiants connus plutôt que d'échapper. Pour les appels au système d'exploitation, passez un tableau d'arguments plutôt qu'une chaîne shell. Le cross-site scripting relève aussi de cette catégorie : l'interpréteur est alors le navigateur, et la correction consiste en un encodage de sortie contextuel assuré par le moteur de templates, complété par une Content Security Policy qui interdit les scripts inline comme seconde ligne de défense.

Conception non sécurisée

Cette catégorie existe pour distinguer les failles de conception des failles de code. Une réinitialisation de mot de passe qui vérifie l'identité par des questions dont un attaquant peut trouver les réponses, un parcours de réservation sans limite sur le nombre de places qu'une session peut bloquer, un circuit de remboursement sans seconde validation — tout cela peut être implémenté sans le moindre défaut et rester exploitable, parce que le mécanisme lui-même est inadapté.

Aucune revue de code ne les corrige. La réponse, c'est la modélisation des menaces avant l'implémentation, des abuse cases rédigés à côté des user stories, des architectures de référence pour les problèmes récurrents comme l'authentification et le paiement, et des limites intégrées dès la conception des parcours plutôt qu'ajoutées après un incident. Considérez « cela fonctionne conformément à la spécification » comme un constat à part entière quand c'est la spécification qui pose problème.

Mauvaise configuration de sécurité

La cause d'exposition la plus fréquente en pratique, et la plus facile à corriger : identifiants d'administration par défaut toujours actifs, pages d'erreur verbeuses affichant des stack traces en production, listing de répertoires activé, endpoint actuator ou de management accessible sans authentification, politique CORS trop permissive, stockage cloud ouvert sur Internet, en-têtes de sécurité absents.

# Profil de production : exposer le minimum, ne rien dire d'utile à un attaquant
server:
  error:
    include-stacktrace: "never"
    include-message: "never"
management:
  endpoints:
    web:
      exposure:
        include: "health,info"
  endpoint:
    health:
      show-details: "when-authorized"

Durcissez en construisant les environnements à partir de code, pour que la configuration puisse être relue et reste identique partout, en supprimant les applications d'exemple et les fonctionnalités inutilisées, en lançant un scan de configuration dans le pipeline et en positionnant les en-têtes de réponse standard — Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options, un Referrer-Policy restrictif. Automatisez : une checklist appliquée à la main dérive en moins d'un mois.

Composants vulnérables et obsolètes

Les applications modernes sont surtout faites du code des autres, et une vulnérabilité connue dans une bibliothèque largement déployée est recherchée à l'échelle d'Internet dans les heures qui suivent sa divulgation. Le risque n'a rien d'exotique : c'est un framework en retard de trois versions majeures, une dépendance transitive dont personne ne soupçonnait la présence, ou une image de base qui n'a pas été reconstruite depuis un an.

Générez un SBOM au moment du build, scannez chaque commit sur une base d'avis de sécurité et automatisez les montées de version courantes pour que les humains ne traitent que les cas difficiles. Privilégiez les bibliothèques activement maintenues, supprimez les dépendances que vous n'utilisez plus et reconstruisez régulièrement les images de conteneurs pour que les paquets du système d'exploitation restent à jour. L'intégration continue est le bon endroit pour tout cela — voir intégration continue pour garder ces vérifications assez rapides pour que personne ne les désactive.

Défaillances d'identification et d'authentification

Les faiblesses dans la preuve de l'identité : du credential stuffing contre une page de connexion sans rate limiting, des identifiants de session qui survivent à un changement de privilèges, des jetons qui n'expirent jamais, un lien de réinitialisation de mot de passe valable plusieurs jours et réutilisable, une authentification multifacteur qu'on peut contourner en appelant un endpoint legacy.

Les contrôles sont bien connus — confronter les mots de passe aux listes de fuites plutôt qu'imposer des règles de composition, proposer une authentification multifacteur résistante au phishing, régénérer l'identifiant de session à la connexion, faire expirer et invalider les sessions à la déconnexion et au changement de mot de passe, rendre les jetons de réinitialisation à usage unique et de courte durée, appliquer des délais croissants plutôt que des verrouillages définitifs, et renvoyer des messages identiques, que le compte existe ou non. Le tableau complet se trouve dans gestion des identités et des accès.

Défaillances d'intégrité des logiciels et des données

Cette catégorie couvre le fait de faire confiance à du code ou à des données dont vous n'avez pas vérifié la provenance : un build qui récupère un plugin depuis une source non figée, un pipeline disposant d'un accès en écriture à la production sans revue de sa propre configuration, un mécanisme de mise à jour automatique sans vérification de signature et — cas d'école au niveau applicatif — la désérialisation de données contrôlées par un attaquant vers des types arbitraires.

// Vulnérable : la désérialisation native d'octets non fiables peut instancier tout ce que le classpath permet
Object payload = new ObjectInputStream(request.getInputStream()).readObject();

// Corrigé : un format de données sans résolution de type, lié à une structure connue
OrderRequest payload = jsonMapper.readValue(request.getInputStream(), OrderRequest.class);

Au-delà, figez les versions des dépendances avec des checksums, signez les artefacts et vérifiez les signatures avant déploiement, soumettez les systèmes de build au même contrôle d'accès que la production, et désactivez la gestion polymorphe des types dans les bibliothèques JSON, sauf si un besoin précis la justifie et qu'une allow-list l'encadre.

Défaillances de journalisation et de surveillance de la sécurité

Ce n'est pas une porte d'entrée, mais c'est la raison pour laquelle une intrusion dure des mois au lieu de quelques heures. Les symptômes sont reconnaissables : échecs de connexion non enregistrés, aucune alerte sur les refus d'autorisation ou les volumes de données inhabituels, logs conservés uniquement sur la machine qui les a produits, et aucune synchronisation d'horloge pour corréler les événements entre services.

Journalisez les tentatives d'authentification, les refus de contrôle d'accès, les changements de privilèges, les actions d'administration et les échecs de validation des entrées à la frontière, avec l'acteur, l'action, la cible, le résultat, l'horodatage et un identifiant de corrélation. Envoyez-les hors de l'hôte, vers un stockage que l'application ne peut pas réécrire, conservez-les assez longtemps pour mener une enquête, et alertez sur des motifs plutôt que sur des événements isolés. Ensuite, entraînez-vous : une alerte à laquelle personne n'a jamais répondu n'est pas un contrôle. Et faites attention à ce que vous écrivez — des mots de passe, des jetons ou des données personnelles dans les logs créent une seconde copie de ces données, moins bien protégée.

Server-side request forgery

Il y a SSRF lorsqu'une application récupère une URL fournie par un utilisateur — la cible d'un webhook, un import d'image, un convertisseur de documents — et que l'attaquant la dirige vers l'intérieur, vers des services internes ou vers un endpoint de métadonnées cloud qui distribue des identifiants à quiconque les demande depuis l'instance.

Atténuez le risque par couches, car la validation d'URL seule est notoirement perméable : validez contre une allow-list d'hôtes autorisés plutôt qu'une deny-list, résolvez le nom d'hôte et vérifiez que l'adresse obtenue est publique avant de vous connecter, désactivez le suivi des redirections ou revalidez chaque saut, exigez le schéma attendu, et imposez au composant qui effectue les requêtes des règles de sortie réseau qui n'autorisent que ce dont il a légitimement besoin. Sur les instances cloud, exigez le service de métadonnées durci qui impose un jeton de session. Renvoyez un résultat générique à l'appelant, pour que le contenu et le temps de réponse ne se transforment pas en scanner de ports interne.

Bien utiliser la liste

Trois habitudes distinguent les équipes qui tirent profit du Top 10 de celles qui se contentent de le citer.

Considérez-le comme un point de départ, pas comme une ligne d'arrivée. C'est un classement de catégories fréquentes, pas une définition de ce qui suffit. L'absence de ces dix faiblesses ne signifie pas qu'une application est sûre ; votre logique métier comporte des risques qu'aucune liste générique ne peut nommer.

Transformez la sensibilisation en exigences. L'Application Security Verification Standard est le document compagnon prévu pour cela : plusieurs centaines d'exigences testables organisées par thème, avec trois niveaux de rigueur pour que chaque équipe choisisse celui qui correspond à son risque. Choisissez un niveau, adoptez les exigences pertinentes comme critères d'acceptation, et vous aurez remplacé « nous avons tenu compte de l'OWASP » par des affirmations qu'un relecteur peut vérifier. L'API Security Top 10 joue le même rôle pour les risques propres aux API.

Automatisez ce qui peut l'être, et faites relire ce qui ne peut pas l'être. Les scans de dépendances et de configuration ont leur place dans le pipeline, aux côtés de l'analyse statique ; les failles de contrôle d'accès et de logique métier exigent des personnes qui comprennent le domaine. Surtout, écrivez un test de non-régression pour chaque défaut de sécurité corrigé, afin que la même faiblesse ne puisse pas revenir à la faveur d'une nouvelle fonctionnalité.

Points clés à retenir

  • Le Top 10 est un classement de sensibilisation des catégories de faiblesses, pas un standard — utilisez l'ASVS lorsque vous avez besoin d'exigences testables.
  • Le contrôle d'accès défaillant est en tête parce qu'il dépend du métier et reste invisible pour les scanners ; refusez par défaut, restreignez les requêtes au principal et testez avec un second utilisateur.
  • L'injection se règle en tenant les données à l'écart de la grammaire de l'interpréteur : requêtes paramétrées, tableaux d'arguments et encodage de sortie contextuel.
  • On ne contourne pas une conception non sécurisée par le code ; la modélisation des menaces et les abuse cases sont la seule correction.
  • La mauvaise configuration et les composants obsolètes sont les catégories où l'automatisation rapporte le plus, grâce à l'infrastructure as code, aux SBOM et au scan des dépendances à chaque commit.
  • La journalisation, la surveillance et une réponse à incident répétée à l'avance déterminent la durée d'une intrusion, et la SSRF exige une défense en couches, à la fois au niveau de l'URL et du réseau.

Sources et lectures complémentaires

Questions fréquentes

L'OWASP Top 10 est-il un standard de sécurité ?
Non, et l'OWASP le dit explicitement. C'est un document de sensibilisation, construit à partir de données sur les catégories de faiblesses les plus fréquentes, destiné à lancer la discussion et à fixer des priorités. Une équipe qui a corrigé les dix a encore du travail devant elle. Si vous avez besoin d'exigences testables — quelque chose qui permette à un relecteur de valider ou de refuser un build —, c'est l'Application Security Verification Standard qui a été écrit dans ce but.
Pourquoi le contrôle d'accès défaillant arrive-t-il toujours en tête ?
Parce qu'on ne le détecte pas par recherche de motifs. Un scanner automatisé voit une requête qui renvoie des données, sans aucun moyen de savoir si l'utilisateur y avait droit ; seul quelqu'un qui comprend le métier peut le dire. Le contrôle d'accès est en outre réparti sur tous les endpoints : une seule nouvelle route ajoutée sans la vérification rouvre le risque, quelle que soit la qualité du reste de l'application.
Comment garder la maîtrise des dépendances vulnérables ?
Générez un SBOM (software bill of materials) au moment du build, lancez un scanner de dépendances à chaque commit sur une base d'avis de sécurité maintenue, et automatisez les montées de version de routine pour que seules les plus délicates demandent une intervention humaine. Figez les versions et vérifiez les checksums pour que le build soit reproductible, supprimez les bibliothèques inutilisées et suivez le graphe des dépendances transitives, pas seulement les déclarations directes.
Quelle différence entre une conception non sécurisée et un bug de sécurité ?
Un bug, c'est une conception correcte mal implémentée — une vérification manquante, une mauvaise comparaison — et il se corrige dans le code. Une conception non sécurisée respecte fidèlement sa spécification mais ne peut pas être rendue sûre, comme une réinitialisation de mot de passe fondée sur des questions dont n'importe qui peut trouver la réponse. Aucun effort de codage ne la corrige : il faut changer le mécanisme, et c'est pourquoi la modélisation des menaces a sa place avant l'implémentation.