La sécurité des applications est plus critique que jamais en 2026. Pourtant, malgré les progrès des outils et des frameworks, on retrouve encore dans le code des erreurs classiques qui ouvrent la porte aux failles. Chaque année, des incidents de sécurité coûteux pour les entreprises sont dus à des oublis ou à de mauvaises pratiques de développement que l’on pensait révolues.
Dans cet article, nous passons en revue les pires erreurs de sécurité que les développeurs commettent encore en 2026. Pour chaque erreur, nous expliquerons le problème, illustrerons avec un exemple de code en français montrant quoi ne pas faire et comment corriger, et donnerons des conseils pour éviter ces pièges. Que vous travailliez sur des applications web, mobiles ou back-end, ces rappels vous aideront à développer des systèmes plus robustes.
Nota bene : Nous nous concentrerons sur les erreurs humaines et les mauvaises pratiques dans le code ou la configuration. Il existe bien sûr des outils de sécurité automatisés pour aider à déceler ces problèmes, mais l’objectif ici est de renforcer votre mentalité "secure by design" et de vous rappeler les bases à respecter au quotidien.
1. Ne pas valider ni assainir les entrées utilisateurs (injections SQL/NoSQL)
Le problème : l'une des erreurs les plus anciennes et pourtant toujours présentes est de faire confiance aux données fournies par l'utilisateur sans les vérifier ni les nettoyer. Si un développeur utilise directement ces entrées dans des requêtes vers une base de données SQL ou NoSQL, ou dans des commandes du système, il ouvre la porte aux injections (SQL, NoSQL, LDAP, commande OS, etc.). Un utilisateur malveillant peut alors injecter du code ou des paramètres spéciaux pour altérer le comportement prévu, par exemple en détournant une requête SQL pour récupérer des données sensibles ou corrompre la base de données.
Pourquoi c'est grave : les injections font partie du top des failles de sécurité depuis des décennies car leurs conséquences peuvent être dramatiques : perte de données, vol d'informations confidentielles (utilisateurs, mots de passe, etc.), prise de contrôle du serveur, etc. En 2026, on voit encore des applications vulnérables aux injections car les entrées ne sont pas correctement validées ou échappées.
Exemple d'erreur : imaginons une API Node.js qui reçoit un nom d'utilisateur en paramètre et l'utilise dans une requête SQL pour chercher un profil :
// Entrée fournie par l'utilisateur (ex: URL ?name=Jean)
const userInput = req.query.name;
// Mauvaise pratique : concaténer directement l'entrée dans la requête SQL
const query = "SELECT * FROM users WHERE name = '" + userInput + "'";
db.query(query, (err, result) => {
// ...
});
Si userInput vaut Jean' OR '1'='1, la requête deviendra :
SELECT * FROM users WHERE name = 'Jean' OR '1'='1'
Ce qui contourne le filtre sur le nom et retourne tous les utilisateurs ! Ici, aucune validation ni échappement n'est effectuée sur la valeur fournie, rendant l'application vulnérable à une injection SQL.
Comment corriger : Toujours valider et nettoyer les données d'entrée, et utiliser des requêtes paramétrées ou des ORM qui évitent la concaténation directe. Par exemple, avec une requête paramétrée en Node.js :
// Bonne pratique : utilisation d'une requête paramétrée
const userName = req.query.name;
const query = "SELECT * FROM users WHERE name = ?";
db.query(query, [userName], (err, result) => {
// ...
});
Ici, ? est remplacé sécuritairement par la valeur, empêchant toute injection. De même, les frameworks modernes proposent des outils de validation (par ex. class-validator en Node, Hibernate Validator en Java) pour vérifier le format des données. Côté front-end, des librairies comme VeeValidate ou Zod (utilisées dans la formation Vue.js) permettent de valider les formulaires avant envoi.
Bonnes pratiques à retenir :
- N'acceptez jamais aveuglément une entrée utilisateur. Validez le format attendu (nombre, email, identifiant existant...) et rejetez ou nettoyez tout le reste.
- Utilisez des API sécurisées pour vos requêtes (requêtes paramétrées, ORM) plutôt que de construire des chaînes de requête dynamiques.
- Envisagez une approche whitelist (accepter uniquement ce qui est permis) plutôt que blacklist (bloquer ce qui est mauvais) pour filtrer les entrées.
- Formez-vous aux bases de la sécurité web (par exemple notre formation Go couvre les fondamentaux comme la validation et la prévention des injections dans un contexte web).
2. Laisser des failles XSS (Cross-Site Scripting)
Le problème : Le XSS est une autre vieille connaissance dans le monde de la sécurité web. Une faille Cross-Site Scripting survient quand une application web permet d'injecter du contenu non fiable (comme du HTML ou JavaScript) dans une page vue par d'autres utilisateurs. En pratique, cela arrive souvent quand on affiche directement, dans une page HTML, une valeur fournie par l'utilisateur sans l'échapper ou la filtrer. Par exemple, un commentaire posté par un utilisateur malveillant pourrait contenir du JavaScript méchant : si l'application ne nettoie pas ce contenu, le script s'exécutera chez tous les visiteurs du commentaire.
Pourquoi c'est grave : Les attaques XSS permettent de voler des informations de l'utilisateur (cookies de session, tokens JWT stockés en localStorage...), de défacer l'interface, de rediriger l'utilisateur vers un site piégé, voire d'installer des logiciels malveillants via le navigateur. En 2026, même avec des frameworks front modernes qui intègrent des protections (par ex. Angular échappe automatiquement les expressions dans les templates), des développeurs introduisent encore des XSS en contournant ces protections ou via des fonctionnalités comme l'insertion de HTML brut.
Exemple d'erreur : Considérons un extrait de code d'un serveur Node/Express qui récupère un paramètre et l'insère tel quel dans une réponse HTML :
app.get('/hello', (req, res) => {
const name = req.query.name;
// Mauvaise pratique : insertion directe de l'entrée dans le HTML répondu
res.send(`<h1>Bonjour ${name} !</h1>`);
});
Si un attaquant appelle /hello?name=<script>alert('XSS')</script>, le navigateur de la victime qui cliquera ce lien affichera une alerte. Le script malveillant s'exécute parce que l'application a inclus la valeur sans la transformer.
Comment corriger : La règle d'or pour prévenir les XSS est d'échapper ou d'assainir tout contenu dynamique injecté dans une page. Dans l'exemple ci-dessus, on devrait par exemple utiliser une fonction d'échappement de HTML (disponible via des bibliothèques comme lodash, ou incluse dans les moteurs de template). Si on avait utilisé un moteur de template (EJS, Pug, Handlebars, etc.), ceux-ci échappent souvent par défaut les variables insérées. En Angular ou Vue, les expressions moustaches {{ }} sont échappées automatiquement.
Cependant, il faut rester vigilant : par exemple la directive v-html de Vue permet d'insérer du HTML brut et peut introduire une faille XSS si on y injecte du contenu non filtré. Ce genre de fonctionnalité doit être utilisé avec parcimonie et uniquement avec du contenu de confiance. Notre formation Vue.js explique notamment comment gérer la sécurité lors de l'insertion de contenu dynamique.
Bonnes pratiques à retenir :
- N'insérez jamais directement du contenu fourni par l'utilisateur dans le DOM sans l'avoir échappé (caractères spéciaux transformés en entités HTML) ou validé.
- Préférez les frameworks/templates qui gèrent l'échappement par défaut, et ne désactivez pas ces protections (n'utilisez pas de fonctions du type
innerHTMLou équivalent sauf nécessité absolue). - Même pour le JavaScript côté client, méfiez-vous des méthodes
evalou de l'interprétation de JSON non fiable qui pourraient exécuter du code. - En cas de contenu riche (ex: commentaire avec mise en forme), utilisez des librairies de sanitization (comme DOMPurify en JS) pour ne conserver que les balises autorisées et supprimer tout script.
3. Mots de passe faibles ou mal gérés (authentification insuffisante)
Le problème : La gestion des mots de passe et de l'authentification est un pilier de la sécurité applicative. Cependant, de nombreuses erreurs persistent :
- autoriser des mots de passe trop simples ou par défaut (ex:
password123,admin/admin), - stocker les mots de passe en clair dans la base de données ou avec un algorithme de hachage obsolète,
- ne pas mettre en place de politiques de complexité ou d'expiration,
- transmettre des mots de passe sans protection (ex: via une connexion non chiffrée),
- réutiliser le même mot de passe pour plusieurs comptes de service (ce qui multiplie les risques en cas de fuite).
Pourquoi c'est grave : Les attaques par credential stuffing (tests de couples login/mot de passe volés sur d'autres sites), le bruteforce sur des comptes sans limitation, ou encore les fuites massives de mots de passe sont monnaie courante. Un mot de passe faible ou compromis peut permettre à un attaquant d'accéder à des comptes utilisateurs, voire d'obtenir des privilèges d'administration. De plus, si la base stocke les mots de passe en clair et qu'elle est compromise, c'est l'intégralité des comptes utilisateurs qui est exposée instantanément.
Exemple d'erreur : Prenons un bout de code où l'on enregistre un nouvel utilisateur dans une application Node.js :
function register(username, password) {
// Mauvaise pratique : on enregistre le mot de passe tel quel en base
db.insert("INSERT INTO users (username, password) VALUES (?, ?)", [username, password]);
}
Ici, si password vaut "MonSecret", c'est exactement cette chaîne qui sera stockée et visible en clair dans la base. Si un attaquant accède à la base, il peut lire tous les mots de passe des utilisateurs.
Comment corriger : Il faut hacher les mots de passe avec un algorithme robuste (par exemple bcrypt, Argon2 ou PBKDF2) et un saupoudrage cryptographique (salt) unique par utilisateur. Ainsi, on ne stocke jamais le mot de passe en clair, mais une empreinte irréversible. Lors de la vérification, on compare le haché du mot de passe fourni à l'inscription ou à la connexion avec celui stocké.
const bcrypt = require('bcrypt');
function register(username, password) {
const saltRounds = 10;
const hash = bcrypt.hashSync(password, saltRounds);
db.insert("INSERT INTO users (username, password_hash) VALUES (?, ?)", [username, hash]);
}
De plus, imposez une politique de mots de passe forts : longueur minimale, mélange de caractères, interdiction des mots de passe trop communs. L'utilisation d'un gestionnaire de mots de passe par les utilisateurs devrait être encouragée. Pensez également à proposer l'authentification multi-facteurs (2FA) pour les opérations sensibles ou les comptes admins, afin de limiter l'impact d'un mot de passe compromis.
Nos formations backend (comme la formation Node.js ou la formation Symfony) abordent en profondeur ces questions d'authentification sécurisée, avec l'utilisation des bonnes bibliothèques et pratiques.
Bonnes pratiques à retenir :
- Ne jamais stocker de mot de passe en clair. Toujours les hacher avec sel et algorithme moderne (bcrypt, Argon2...).
- Mettre en place une politique de complexité de mot de passe et, si possible, un deuxième facteur d'authentification.
- Ne pas réutiliser les mêmes secrets pour différents services. Par exemple, si votre application utilise une API externe, ne pas utiliser le même mot de passe/clé d'API que pour un autre service.
- Protéger les canaux d'authentification : toujours envoyer les mots de passe via une connexion chiffrée (HTTPS). Les cookies de session doivent être marqués HttpOnly (pour ne pas être lisibles en JavaScript) et Secure (transmis seulement sur HTTPS).
4. Contrôles d'accès insuffisants (autorisations manquantes)
Le problème : Même si l'authentification est correcte, il faut encore s'assurer que les utilisateurs ne puissent pas outrepasser leurs droits. Une erreur fréquente est de ne pas vérifier les autorisations sur certaines actions ou accès à des ressources. Par exemple, une application peut permettre de récupérer les détails d'un utilisateur via son ID dans l'URL, mais si elle ne vérifie pas que l'utilisateur connecté est autorisé à voir ces détails, n'importe qui peut potentiellement accéder aux données d'un autre en devinant son ID (on parle d'IDOR, Insecure Direct Object Reference). De même, oublier de restreindre certaines fonctions admin aux seuls admins est une erreur courante.
Pourquoi c'est grave : Les failles de Broken Access Control sont régulièrement en tête du classement OWASP Top 10. Sans vérification d'autorisations, des utilisateurs malveillants ou curieux peuvent accéder à des informations sensibles d'autrui (données personnelles, documents privés) ou effectuer des actions qu'ils ne devraient pas (modifier les données d'un autre, escalade de privilège). C'est un risque majeur de fuite de données et d'atteinte à la confidentialité.
Exemple d'erreur : Supposons une API Express/Node qui offre un endpoint pour récupérer le profil d'un utilisateur par ID :
// Endpoint vulnérable : aucune vérification de l'utilisateur connecté
app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
const userData = db.findUserById(userId);
res.json(userData);
});
Ici, rien ne vérifie que l'utilisateur authentifié req.user correspond à l'ID demandé ou a un rôle admin. Un utilisateur malveillant pouvant deviner un autre ID (/api/users/123) obtiendra les informations de l'utilisateur 123.
Comment corriger : Toujours appliquer le principe du moindre privilège et vérifier les droits. Dans notre exemple, il faudrait vérifier que req.user.id === req.params.id (si c'est une ressource appartenant à l'utilisateur) ou que req.user.role inclut une permission d'accès. Par exemple :
// Endpoint sécurisé avec middleware d'auth et vérification d'autorisations
app.get('/api/users/:id', authenticate, (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Accès interdit' });
}
const userData = db.findUserById(req.params.id);
res.json(userData);
});
Ici on utilise un middleware authenticate qui peuple req.user (d'après le token/cookie de session), puis on compare l'ID. Si l'utilisateur n'est pas lui-même ou admin, on renvoie une erreur 403.
Il est recommandé d'utiliser les mécanismes fournis par les frameworks pour la gestion des autorisations : par exemple Spring Security (pour Java/Spring, comme pratiqué dans notre formation Spring Boot) permet de déclarer aisément les rôles requis par endpoint ou même par méthode de service. De même, Symfony ou Django ont des systèmes de permissions et de groupes. L'important est de toujours penser à protéger chaque point d'entrée de l'application (routes API, pages, actions) par une vérification du droit de l'utilisateur courant.
Bonnes pratiques à retenir :
- Adoptez le principe du moindre privilège : un utilisateur ne doit avoir que les droits nécessaires à ses tâches, pas plus.
- Vérifiez systématiquement, côté serveur, que l'utilisateur authentifié a le droit d'accéder à la ressource ou d'effectuer l'action demandée. Ne jamais se fier uniquement à la logique côté client pour masquer des fonctionnalités.
- Utilisez les frameworks et leurs modules de sécurité pour gérer les permissions (par ex. les décorateurs @PreAuthorize de Spring Security, les Voters de Symfony, etc.) afin d'avoir une approche centralisée et facile à maintenir.
5. Exposition de données sensibles (absence de chiffrement)
Le problème : Les développeurs traitent souvent des données sensibles (mots de passe, numéros de carte bancaire, informations personnelles, dossiers médicaux, etc.). Une erreur encore trop fréquente est de ne pas protéger suffisamment ces données, que ce soit en transit ou au repos. Par exemple, une application mobile qui communique avec une API sans HTTPS enverra potentiellement des données personnelles en clair sur le réseau. Côté serveur, ne pas chiffrer certains champs sensibles en base (ou l'intégralité de la base quand c'est possible) peut rendre toutes les données lisibles instantanément par un attaquant en cas de breach.
Pourquoi c'est grave : La confidentialité des utilisateurs est en jeu. Des données personnelles exposées peuvent mener à du vol d'identité, de la fraude financière (si numéros de cartes ou IBAN sont dérobés), ou simplement une perte de confiance irréversible de la part des clients. Sans chiffrement, un attaquant qui intercepte le trafic ou accède à la base voit tout en clair.
Exemple d'erreur : Imaginons un service de paiement où le développeur stocke les numéros de carte de crédit pour faciliter les paiements récurrents :
// Mauvais exemple : la carte est stockée en clair
const cardNumber = req.body.cardNumber;
database.save({ userId: req.user.id, card: cardNumber });
Si la base est compromise, tous les numéros de carte sont exposés. De même, si l'appel à ce service n'était pas chiffré (pas de TLS), un attaquant sur le réseau aurait pu voir passer ces numéros.
Comment corriger : Appliquer le chiffrement partout où c'est pertinent :
- Utilisez HTTPS pour toutes les communications (grâce à TLS). Des services comme Let's Encrypt facilitent l'obtention de certificats gratuitement, et les serveurs web modernes (NGINX, Apache) ou les services cloud gèrent bien TLS (voir la formation NGINX pour apprendre à configurer HTTPS par exemple).
- Chiffrez les données sensibles en base ou dans les fichiers : soit au niveau applicatif (avec une clé de chiffrement maîtrisée par l'application), soit via des fonctionnalités de la base (certaines bases proposent le chiffrement transparent des colonnes ou du disque).
- Ne détenez que le strict nécessaire : par exemple, évitez de stocker vous-même des cartes bancaires, passez plutôt par des fournisseurs de paiement qui tokenisent ces informations (Stripe, Braintree...).
Bonnes pratiques à retenir :
- Activez toujours TLS (HTTPS) sur vos applications web et API. En 2026, tout trafic en clair est suspect.
- Identifiez les données sensibles que gère votre application (données personnelles RGPD, secrets, infos financières, santé, etc.) et assurez-vous qu'elles sont chiffrées en stockage (ou au moins fortement protégées par des contrôles d'accès stricts).
- Stockez séparément les clés de chiffrement, idéalement hors du code source (services de type Key Management Service sur le cloud, ou HashiCorp Vault en entreprise). Les clés elles-mêmes doivent être régulièrement rotées.
6. Exposer des secrets et clés API dans le code ou le dépôt
Le problème : combien de fois a-t-on vu sur GitHub ou dans des archives de code source une clé API, un mot de passe de base de données ou un secret d'authentification laissé en clair ? Hardcoder des secrets dans le code (par exemple comme constantes), ou ne pas filtrer les fichiers de configuration sensibles lors d'un commit git, constitue une erreur de sécurité majeure. Parfois, des développeurs laissent même leurs identifiants cloud ou certificats privés dans un dépôt public par inadvertance, ce qui peut mener à des compromissions catastrophiques (ex: un attaquant récupère la clé AWS et lance des dizaines de serveurs à vos frais).
Pourquoi c'est grave : les secrets doivent rester... secrets. Une clé d'API exposée peut être utilisée par n'importe qui pour consommer votre API avec vos quotas ou accéder à vos données. Un mot de passe de base de données dans le code donne directement accès à votre base si quelqu'un obtient le code. Même en interne, partager les secrets en clair multiplie les risques de fuite par ingénierie sociale ou par erreur humaine.
Exemple d'erreur : dans un fichier de configuration JavaScript :
export default {
db: {
host: 'db.myapp.local',
user: 'admin',
password: 'SuperSecretPassword' // Mauvais : mot de passe en clair
},
apiKeyStripe: 'sk_live_51Hq...abc' // Mauvais : clé privée Stripe exposée
};
Ou bien un fichier .env committé dans le dépôt git :
AWS_ACCESS_KEY_ID=AKIAI...GJBQ
AWS_SECRET_ACCESS_KEY=Wqw9...Ztz
Si ce dépôt devient public ne serait-ce que brièvement, les bots qui scrutent GitHub trouveront ces valeurs en quelques minutes.
Comment corriger : adoptez une stratégie de gestion des secrets :
- Ne stockez pas les secrets en clair dans le code. Utilisez des variables d'environnement pour injecter les secrets (éventuellement via un fichier
.envqui n'est pas versionné). Des librairies comme dotenv en Node, python-dotenv en Python, ou les systèmes de configuration des frameworks (Symfony, Spring Boot, etc.) permettent de charger ces variables sans les écrire en dur. - Si possible, utilisez des services de stockage de secrets : par exemple Vault (intégré à notre formation Ansible pour la gestion des mots de passe) ou les Secret Managers des fournisseurs cloud (AWS Secrets Manager, Azure Key Vault, etc.). Ils offrent du chiffrement et un contrôle d'accès granulaires.
- Mettez en place des filtres dans vos outils de CI/CD (comme vu dans la formation GitHub Actions sur l'utilisation des Secrets GitHub) afin de ne jamais loguer ou exposer par inadvertance des secrets durant les déploiements.
- En cas de fuite d'un secret, considérez-le comme compromis : révoquez-le immédiatement (rotate) et surveillez les usages abusifs.
Bonnes pratiques à retenir :
- Faites l'inventaire de tous vos secrets (clés API tierces, identifiants BDD, certificats, tokens OAuth...) et assurez-vous qu'aucun ne réside dans le code source ou un dépôt public.
- Utilisez des fichiers de configuration séparés, non inclus dans le versioning, pour ces secrets, et fournissez des exemples non sensibles (« .env.example ») pour la configuration par défaut.
- Restreignez les droits attachés à chaque clé : par exemple, une clé d'API Google Maps ne doit pouvoir accéder qu'à l'API Maps, pas à tous vos services Google Cloud. Ainsi, même si une clé fuit, son impact est limité.
7. Laisser des identifiants par défaut ou des comptes de test actifs
Le problème : beaucoup d'outils, de bases de données ou d'applications possèdent des comptes par défaut (ex: admin/admin, root/root) ou des comptes de test créés durant le développement (ex: un utilisateur "test" avec mot de passe "test"). Si ces comptes ne sont pas supprimés ou modifiés avant la mise en production, ils offrent une porte d'entrée évidente aux attaquants, qui connaissent bien les logins par défaut courants. Par ailleurs, certaines configurations par défaut (ports ouverts, services exposés sans authentification) peuvent demeurer si on n'y prend garde.
Pourquoi c'est grave : un compte administrateur par défaut non changé, c'est comme laisser la clé sous le paillasson. De nombreux piratages automatisés scannent des milliers de serveurs à la recherche de panneaux d'administration avec des identifiants connus (exemple classique : admin/admin sur une interface web WordPress, ou root sans mot de passe sur un serveur MySQL). De même, un compte de test oublié peut être exploité s'il a des privilèges élevés.
Exemple d'erreur : déployer une application Node.js dont la base de données utilise toujours l'utilisateur par défaut :
// Mauvais : on utilise l'utilisateur root sans mot de passe par défaut
const db = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '', // mot de passe vide !
database: 'ma_base'
});
Ou laisser activé en production un compte générique créé pour les tests d'intégration (par ex. un utilisateur demo visible par tous avec un mot de passe trivial).
Comment corriger : il faut impérativement :
- Changer ou désactiver les comptes par défaut dès l'installation d'un logiciel ou d'une base de données. Donnez un mot de passe fort aux comptes nécessaires, et supprimez ou bloquez les autres.
- Supprimer les comptes de test/demo et les jeux de données de test avant de passer en production. Si vous avez besoin d'un environnement de démo, isolez-le de la production.
- Vérifier la configuration par défaut des services : par exemple, assurez-vous qu'aucune interface d'administration n'est exposée publiquement sans authentification, que les ports non nécessaires sont fermés, etc. Les formations DevOps (comme la formation AWS ou Docker) vous apprennent à bien configurer et durcir vos environnements.
8. Ne pas mettre à jour les dépendances (composants vulnérables)
Le problème : une application moderne repose sur de nombreuses bibliothèques, frameworks et outils tiers. Lorsqu'une vulnérabilité est découverte dans l'un de ces composants (et cela arrive fréquemment), les développeurs du projet open-source publient généralement un correctif (patch). Mais si vous n'appliquez pas ces mises à jour, votre application reste exposée à la faille connue. Ne pas maintenir ses dépendances à jour est donc une erreur courante qui garde ouvertes des brèches de sécurité pourtant documentées.
Pourquoi c'est grave : les attaquants scrutent les bulletins de sécurité et les changelogs des bibliothèques populaires. Dès qu'une faille est annoncée, ils essaient de l'exploiter à grande échelle sur toutes les applications qui n'ont pas encore mis à jour. Par exemple, la faille "Log4Shell" fin 2021 a touché des millions de serveurs Java, mais ceux ayant rapidement mis à jour Log4j ont échappé aux exploits automatisés. En 2026, on trouve encore des applications utilisant des versions obsolètes contenant des failles critiques, faute de maintenance.
Exemple d'erreur : vous développez une application Node.js et ignorez les alertes de npm audit ou yarn audit qui signalent une vulnérabilité XSS dans une de vos dépendances front. Ou bien vous avez une application Python Django mais vous restez sur la version 3.x alors que des failles sévères ont été corrigées en 4.x, simplement par souci de ne pas "tout casser" en mettant à jour.
Comment corriger : Mettre en place un processus rigoureux de gestion des mises à jour :
- Surveillez les annonces de sécurité pour les technologies que vous utilisez (abonnez-vous aux newsletters ou flux RSS de sécurité, utilisez des outils de veille).
- Testez et appliquez régulièrement les mises à jour de sécurité (pour les bibliothèques, mais aussi l'OS, le serveur web, etc.). Les outils de CI/CD peuvent aider à détecter les dépendances à jour (ex: Dependabot sur GitHub).
- Si une mise à jour majeure risque de briser du code, planifiez le refactoring nécessaire. Il vaut mieux investir du temps à adapter son code que de laisser tourner une librairie vulnérable. Nos formations mettent à jour régulièrement les projets aux dernières versions (par exemple, la formation Angular et la formation React couvrent les versions récentes et leurs améliorations de sécurité).
9. Oublier les en-têtes HTTP de sécurité (CORS, CSP, HSTS, etc.)
Le problème : la sécurité web ne repose pas que sur le code applicatif, mais aussi sur certaines configurations HTTP. Des en-têtes (headers) spéciaux permettent de durcir la sécurité côté navigateur ou client. Par exemple, Content-Security-Policy (CSP) réduit les risques de XSS en limitant les sources de scripts, Strict-Transport-Security (HSTS) force l'usage du HTTPS, X-Frame-Options empêche le clickjacking en interdisant d'afficher le site dans une iframe externe, etc. Oublier d’envoyer ces en-têtes ou mal les configurer prive votre application d'une couche de protection supplémentaire.
Pourquoi c'est grave : sans CSP, un XSS pourra plus facilement charger un script externe malveillant. Sans HSTS, un attaquant pourrait tenter un downgrade de l'utilisateur en HTTP pour espionner (si celui-ci tape explicitement http://). Ce sont des protections complémentaires qui, si absentes, laissent le champ libre à certaines attaques pourtant aisément mitigées.
Exemple d'erreur : déployer son application web sans utiliser aucun header de sécurité, ou avec des valeurs trop permissives. Par exemple, certains développeurs mettent en place Content-Security-Policy: default-src * (tout est permis) juste pour "que ça marche", annulant tout l'intérêt de CSP. Ou laisser Access-Control-Allow-Origin: * sur toutes les ressources API (voir point suivant sur CORS).
Comment corriger : identifiez les en-têtes utiles à votre contexte et activez-les :
- CSP : définissez une politique stricte (au minimum
default-src 'self';pour n'autoriser que votre domaine par défaut, puis ouvrez explicitement ce qui est nécessaire comme les domaines d'APIs, CDN, etc.). Vous pouvez utiliser les outils de rapports de violation CSP pour ajuster sans tout casser. - HSTS : envoyez
Strict-Transport-Security: max-age=31536000; includeSubDomainspour indiquer aux navigateurs de toujours forcer HTTPS sur votre domaine. - X-Frame-Options :
DENYouSAMEORIGINpour empêcher l'embedding du site. - X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy, etc. : configurez ces headers selon les besoins pour réduire la surface d'attaque.
- En pratique, la plupart de ces headers peuvent être ajoutés facilement via votre serveur web (NGINX/Apache) ou via un middleware dans votre appli (par ex. le middleware Helmet en Express/Node ajoute d'un coup plusieurs protections par headers). Notre formation Go aborde par exemple la mise en place de TLS, de CORS et de protections CSRF, tandis que la formation NGINX montre comment configurer les headers HTTP importants côté serveur.
10. Mauvaise configuration de CORS (Cross-Origin Resource Sharing)
Le problème : le CORS est le mécanisme qui définit quelles origines (domaines) externes sont autorisées à faire des requêtes vers votre API ou site. Pour débugger un front local, des développeurs peu regardants mettent parfois Access-Control-Allow-Origin: * sur l'API, autorisant toute origine sans distinction. Ou ils autorisent un domaine précis mais en oubliant d'exclure les cookies (avec credentials: true couplé à * ce qui est interdit et inutile). Une configuration laxiste de CORS peut permettre à des sites tiers d'accéder librement à vos API comme si c'était votre front officiel.
Pourquoi c'est grave : si n'importe quel site peut faire des requêtes XHR/Fetch vers vos endpoints, alors un attaquant peut piéger un utilisateur connecté à votre site en l'incitant à visiter une page malveillante qui va faire des appels à votre API à sa place. Par exemple, si votre API bancaire autorise *, un site pirate pourrait, via le navigateur de la victime (qui a ses cookies de session), appeler l'endpoint de virement d'argent. CORS est justement là pour empêcher ce genre de scénario, à condition de bien le configurer.
Exemple d'erreur : dans une application Express (Node.js) on utilise le middleware CORS de manière trop large :
const cors = require('cors');
// Mauvais : autorise toutes les origines, y compris avec envoi de cookies
app.use(cors({ origin: '*', credentials: true }));
Ici credentials: true indique au navigateur qu'il peut envoyer les cookies de session lors des requêtes cross-origin, mais origin: '*' n'est pas compatible (le navigateur bloquera, * ne fonctionne pas avec credentials true). Le développeur pourrait alors mettre origin: '*' sans credentials sur certaines routes, ce qui autorise certes les GET publics depuis n'importe où, mais peut être dangereux si on expose accidentellement des données.
Comment corriger : règle de base : n'ouvrez que ce qui est nécessaire. Si votre front est sur https://monfront.com, alors configurez CORS pour n'autoriser que monfront.com. Par exemple :
app.use(cors({ origin: 'https://monfront.com', credentials: true }));
Ainsi, seuls les scripts provenant de monfront.com pourront appeler vos API avec les cookies d'authentification. Si vous avez plusieurs origines légitimes (prod, recette, dev local), listez-les explicitement ou utilisez une fonction dynamique pour vérifier l'origine. Évitez le joker * sauf pour des ressources vraiment publiques (et encore, même là c'est pas toujours utile).
Enfin, ne confondez pas CORS (contrôle côté navigateur des origines JavaScript autorisées) avec la sécurité des API en général. Même si votre CORS est très restrictif, un attaquant peut toujours appeler votre API directement (sans passer par un navigateur) s'il n'y a pas d'authentification/autorisation. Le CORS protège surtout les utilisateurs contre certaines attaques CSRF via XHR. Pour en apprendre plus sur la configuration serveur et la sécurité des APIs web, vous pouvez consulter la formation NGINX ou notre formation Node.js qui abordent ces aspects.
11. Absence de protection contre le CSRF (Cross-Site Request Forgery)
Le problème : le CSRF est une attaque où un site malveillant fait exécuter à la victime une action à son insu sur un autre site où elle est authentifiée. Par exemple, sans protections, un simple formulaire caché ou image peut suffire à faire envoyer une requête POST depuis le navigateur de la victime vers un site où elle a une session ouverte. Si l'application ne vérifie pas que la requête vient d'une source légitime, l'attaque réussit. L'erreur côté développeur est de ne pas implémenter de token CSRF ou de vérification d'origine pour les actions à risque.
Pourquoi c'est grave : une attaque CSRF peut avoir des conséquences similaires à un vol de session : l'attaquant fait faire à la victime des actions à sa place. Par exemple, valider une transaction, changer son email, poster un message à sa place, voire changer son mot de passe. Tout cela sans jamais connaître les identifiants, juste en exploitant la confiance du navigateur qui envoie les cookies de session.
Exemple d'erreur : une application web avec un formulaire de changement de mot de passe :
<form action="/user/password" method="POST">
<input type="hidden" name="newpassword" value="motdepassePirate" />
<button type="submit">Changer mon mot de passe</button>
</form>
Si ce formulaire est préparé par un site tiers et que l'utilisateur authentifié sur votre site le visite, alors le bouton (ou même un script qui le soumet automatiquement) enverra la requête POST malveillante à votre site, qui changera le mot de passe sans se douter de la supercherie. Aucun jeton CSRF n'est présent ici pour vérifier l'intention.
Comment corriger :
- Implémentez des jetons CSRF pour les actions critiques via formulaire ou requêtes modifiant l'état. La plupart des frameworks web proposent un mécanisme clef-en-main (par ex. les formulaires Symfony gèrent un token CSRF automatiquement, Angular inclut un XSRF-TOKEN par cookie et en-tête X-XSRF-TOKEN).
- Vérifiez l'en-tête Referer/Origin des requêtes POST sensibles. Si l'origine ne correspond pas à votre domaine, refusez la requête (en complément du token CSRF).
- Envisagez d'utiliser les cookies SameSite=strict pour vos cookies de session, ce qui empêche leur envoi dans les requêtes cross-site. Attention cependant, SameSite peut impacter d'autres fonctionnalités (partage de login entre sous-domaines, etc.).
- Comme toujours, sensibilisez-vous via la documentation OWASP ou nos formations back-end à ces attaques (par exemple la formation Go mentionnée plus haut qui couvre CSRF, ou encore les chapitres sécurité de la formation Spring Boot).
12. Absence de limitation des taux ou de protection contre les abus (Bruteforce, DoS)
Le problème : une application peut fonctionner parfaitement en conditions normales mais être vulnérable aux abus si on ne met pas en place de garde-fous. Ne pas limiter le nombre de tentatives sur une page de login ouvre la voie au bruteforce (essai de milliers de mots de passe). Ne pas restreindre les appels à une API publique peut permettre une surcharge (attaque DoS) ou l'extraction massive de données par scrapping. L'erreur est de supposer que les utilisateurs resteront dans une utilisation légitime et de ne prévoir aucun compteur ou blocage.
Pourquoi c'est grave : le bruteforce peut mener au compromis de comptes (surtout si les mots de passe sont faibles, cf. point sur mots de passe). Un DoS applicatif peut rendre votre service indisponible, ce qui est en soi une faille de disponibilité. Même sans aller jusqu'au DoS volontaire, l'absence de quotas favorise des usages malveillants (robots qui aspirent tout votre contenu, générant de la charge et des coûts).
Exemple d'erreur : une route de login en Express sans aucune limitation :
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (authenticate(username, password)) {
res.send('OK');
} else {
res.status(401).send('Unauthorized');
}
});
Un script automatisé pourrait appeler /login des millions de fois avec des mots de passe différents jusqu'à trouver la bonne combinaison. De même, un bot pourrait envoyer des milliers de requêtes par minute sur n'importe quel endpoint sans être ralenti.
Comment corriger :
- Mettez en place un rate limiting (limitation de taux) sur les actions sensibles. Par exemple, limiter à 5 tentatives de login par 15 minutes et par IP/utilisateur. Il existe des middleware tout faits (ex:
express-rate-limiten Node, des filtres dans Spring Security, etc.). - Surveillez et détectez les comportements anormaux (beaucoup de requêtes à la suite) et prévoyez une réponse adaptative (ex: blocage temporaire du compte ou de l'IP, captcha, augmentation du délai de réponse).
- Pour les API publiques, envisagez des quotas par token/compte et une protection contre les abus (un WAF - Web Application Firewall - peut aider sans toucher au code, mais c'est un outil à part).
- Nos formations cloud et backend (ex: formation AWS pour la configuration de WAF/API Gateway, ou les modules sécurité des formations Node/Java) abordent la mise en place de ces limitations.
13. Fuites d'informations sensibles dans les logs ou messages d'erreur
Le problème : pour débugger, les développeurs loggent souvent beaucoup d'informations. Mais si on n'y prend garde, ces logs peuvent contenir des données confidentielles (mots de passe en clair, tokens, numéros de CB) qui se retrouvent stockées de façon non sécurisée. De même, laisser l'application en mode debug en production ou renvoyer aux utilisateurs des messages d'erreur très détaillés (stack trace complète, requête SQL générée, etc.) constitue une fuite d'information précieuse pour un attaquant.
Pourquoi c'est grave : des logs exposés ou volés peuvent révéler des secrets (ex: une ligne de log "Connexion de l'utilisateur X avec mot de passe Secret123" est plus fréquente qu'on ne croit...). Les messages d'erreur non maîtrisés peuvent trahir la structure interne de l'application, donnant des indications sur les technos utilisées, les requêtes SQL (donc la structure de la base) ou même le code source dans certains dumps d'exception. Un attaquant pourra s'en servir pour affiner ses attaques (savoir quel champ injectionner, quel module est présent, etc.).
Exemple d'erreur : laisser le flag debug activé dans un framework web :
# settings.py de Django
DEBUG = True # Mauvais : en prod, cela affichera la stack trace complète en cas d'erreur
Ou dans un code Node :
console.log("Utilisateur connecté : ", user, " avec mot de passe ", password);
// Mauvais : le mot de passe en clair est loggué dans la console/les logs
Comment corriger :
- Ne logguez jamais de données sensibles en clair. Si vous devez tracer des identifiants, masquez-les ou hashz-les. Par exemple, logguer qu'un utilisateur X s'est connecté, mais pas avec quel mot de passe.
- Désactivez les modes debug ou verbose en production. Configurez le niveau de log minimal (par ex. WARNING au lieu de DEBUG) sur vos environnements prod.
- Personnalisez les pages d'erreur pour qu'elles n'affichent pas de renseignements techniques aux utilisateurs (une page 500 générique plutôt qu'une exception brute).
- Protégez l'accès aux fichiers de log et purgez-les régulièrement des informations sensibles si par mégarde il y en a.
- Lors de nos formations, nous insistons sur la gestion des erreurs et le logging adapté (également abordé dans la formation Spring Boot pour la partie gestion des erreurs), afin de ne pas exposer d'informations critiques.
14. Stockage côté client non sécurisé (mobile, front-end)
Le problème : tout ce qui est stocké sur le dispositif de l'utilisateur (application mobile, application desktop ou même navigateur) est potentiellement accessible si quelqu'un prend le contrôle du device ou en analyse le contenu. Une erreur classique est de stocker des données sensibles en clair côté client. Par exemple, enregistrer un jeton d'authentification longue durée dans un AsyncStorage non chiffré d'une application React Native, ou dans LocalStorage côté navigateur sans protection. Sur mobile natif, ça peut être utiliser les SharedPreferences Android de base au lieu du Keystore sécurisé, ou stocker des fichiers confidentiels sans chiffrement.
Pourquoi c'est grave : si le téléphone de l'utilisateur est compromis (perdu, volé ou mal protégé), ou si une app malveillante s'exécute sur le même appareil, ces données en clair peuvent être lues. Un token d'auth stocké en clair peut être volé et utilisé pour usurper l'identité de l'utilisateur. De plus, sur Android comme iOS, il existe des techniques de rétro-ingénierie (décompilation) qui permettent de déceler les clés ou secrets intégrés à l'application si ceux-ci ne sont pas stockés sécuritairement.
Exemple d'erreur : un développeur Android qui sauvegarde le token JWT de session de l'utilisateur ainsi :
// Dans une application Android (Java/Kotlin)
SharedPreferences prefs = context.getSharedPreferences("MonAppli", MODE_PRIVATE);
prefs.edit().putString("authToken", jwtToken).apply(); // Mauvais : stocké en clair
N'importe quelle autre appli ou personne ayant accès à l'appareil pourra extraire ce fichier de prefs et récupérer le token. Il vaudrait mieux utiliser EncryptedSharedPreferences (sur Android) ou le Trousseau (Keychain) sur iOS.
Comment corriger :
- Sur mobile natif, utilisez les stockages sécurisés proposés par l'OS : le Keystore Android (via EncryptedSharedPreferences ou KeyStore API) et le Keychain iOS, qui chiffrent les données et peuvent restreindre l'accès (par empreinte digitale, etc.).
- Sur mobile hybride (React Native, Flutter), appuyez-vous sur des plugins sécurité qui exposent ces stockages natifs sécurisés au lieu d'utiliser AsyncStorage/SharedPreferences bruts.
- Côté web, réfléchissez à la nécessité de stocker certaines infos. Évitez de conserver des tokens très sensibles dans le navigateur. Par exemple, un token JWT de session peut être mis dans un cookie HttpOnly plutôt qu'en localStorage pour éviter l'accès via JavaScript (et donc réduire l'impact XSS).
- Ne jamais coder en dur des secrets dans une app cliente en pensant qu'ils sont cachés - un attaquant motivé trouvera en décompilant. Si votre app mobile a besoin d'une clé API, stockez-la côté serveur et faites appeler le serveur.
- Notre formation Android couvre les bonnes pratiques de sécurité mobile (réseaux et stockage), et de manière générale, il est conseillé de toujours se demander : "Que se passe-t-il si l'utilisateur malintentionné possède l'appareil ou le code ?".
15. Utiliser des générateurs pseudo-aléatoires non sécurisés
Le problème : pour générer des tokens de session, des liens de réinitialisation de mot de passe, ou tout autre secret temporaire, il faut de l'aléatoire cryptographiquement sûr. Or, certains développeurs utilisent par mégarde des fonctions pseudo-aléatoires prévisibles (par ex. Math.random() en JavaScript, rand() en C) au lieu d'appels à un générateur sécurisé lié au système (comme /dev/urandom ou des API haut niveau). La conséquence : les tokens générés peuvent être prédictibles ou avoir une entropie insuffisante.
Pourquoi c'est grave : si vos identifiants uniques ou tokens de récupération de mot de passe sont prévisibles, un attaquant peut les deviner et ainsi les exploiter (par exemple deviner l'URL de réinitialisation de mot de passe d'un utilisateur en testant toutes les combinaisons possibles si celles-ci sont trop peu nombreuses). De même, un cookie de session prévisible revient à un cookie compromettant la session.
Exemple d'erreur : générer un token avec une simple fonction aléatoire peu sécurisée :
// Mauvais exemple : utilisation de Math.random() pour un token
function generateToken() {
return Math.random().toString(36).substring(2);
}
Math.random() n'est pas conçu pour la sécurité : il produit des valeurs prévisibles. Un attaquant pourrait parcourir l'espace de valeurs s'il est restreint.
Comment corriger : utilisez les API sécurisées fournies par votre langage ou OS :
- En JavaScript (Node ou navigateur moderne) : préférez
crypto.randomBytes()oucrypto.getRandomValues()qui s'appuient sur un générateur cryptographique. - En Python : le module
secretsouos.urandomau lieu derandom.random. - En Java :
SecureRandomplutôt queRandom. - En C/C++ : la fonction
rand()est à proscrire pour du sécuritaire, utilisez les fonctions spécialisées (ex:RAND_bytesd'OpenSSL, ou /dev/urandom via un appel système). - De manière générale, dès qu'un générateur est présenté comme "cryptographically secure" dans votre environnement, utilisez-le pour tout ce qui doit être inconnu d'un adversaire.
16. "Inventer" son propre chiffrement ou mécanisme de sécurité maison
Le problème : par excès de confiance ou méconnaissance des bibliothèques existantes, certains développeurs entreprennent de coder eux-mêmes des algorithmes de chiffrement, des protocoles d'authentification, ou d'autres mécanismes de sécurité complexes. Malheureusement, la cryptographie est un domaine très piégeux, et "faire maison" aboutit presque toujours à des failles. Par exemple, tenter de chiffrer soi-même des données avec une approche naïve (XOR, ou un algorithme inventé ad hoc) sans respecter les principes établis (comme l'utilisation d'un vecteur d'initialisation, un mode d'opération sécurisé, etc.) conduit à un chiffrement faible.
Pourquoi c'est grave : la plupart des implémentations amateurs de crypto sont cassables, parfois en quelques minutes. Un schéma d'authentification custom peut avoir des failles logiques. En ignorant involontairement des attaques connues (rejeu, analyse de fréquence, timing attacks, etc.), on crée un faux sentiment de sécurité. Pendant ce temps, des bibliothèques open-source éprouvées (OpenSSL, libsodium, Bcrypt, etc.) existent et sont auditées par des experts.
Exemple d'erreur : au lieu d'utiliser AES, un développeur concocte sa propre fonction de chiffrement :
// Mauvais exemple : "chiffrement" maison par simple XOR
function xorEncrypt(data, key) {
let res = "";
for (let i = 0; i < data.length; i++) {
res += String.fromCharCode(data.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return res;
}
Il pense sa méthode sûre car "c'est maison". Sauf que le XOR seul (façon chiffrage de Vernam mal implémenté) est trivial à casser si la longueur de clé est inférieure aux données ou réutilisée.
Comment corriger : ne réinventez pas la roue en matière de sécurité !
- Utilisez les librairies et protocoles standard. Par exemple, si vous avez besoin de chiffrer des données, utilisez AES-GCM via une bibliothèque reconnue (comme
cryptoen Node, ou libsodium, etc.) au lieu d'inventer un algo. - Pour l'authentification, reposez-vous sur des protocoles éprouvés (OAuth2, OpenID Connect, etc. le cas échéant) ou les frameworks (Spring Security, etc.) plutôt que de faire un système "maison".
- Documentez-vous sur les attaques classiques avant de penser avoir une idée "révolutionnaire" en sécu. Une bonne pratique est de suivre les recommandations OWASP et les cours spécialisés (par exemple, nos cours sur les frameworks backend montrent comment s'appuyer sur les briques de sécurité existantes).
- Si vraiment vous devez implémenter un mécanisme délicat, faites-le relire et tester par des experts ou la communauté open-source. Ne restez pas seul juge de la robustesse de votre solution.
Conclusion
La sécurité applicative est l'affaire de tous les développeurs. En 2026, les attaques évoluent (on parle d'AI malveillante, de supply chain attacks sophistiquées, etc.), mais comme on l'a vu, beaucoup de failles proviennent encore d'erreurs bénignes – et évitables – dans notre code et nos configurations. La bonne nouvelle, c'est qu'avec de la vigilance et en appliquant systématiquement les bonnes pratiques, on peut éliminer la majorité de ces vulnérabilités avant même qu'elles ne soient exploitées.
Pour un développeur intermédiaire, le maître-mot doit être : anticiper. Anticiper les entrées malveillantes, les comportements inattendus, les usages détournés de votre application. Intégrer la sécurité dès la conception et le développement, plutôt que de la rajouter en catastrophe après coup. Et surtout, se former régulièrement : les technologies changent, mais les principes de base de la sécurité restent pertinents.
En appliquant les leçons tirées de ces pires erreurs de sécurité, vous protégerez mieux vos projets et vos utilisateurs. N'hésitez pas à approfondir ces sujets via des ressources de qualité. Chez Dyma.fr, de nombreuses formations intègrent ces aspects de sécurité (de la gestion des utilisateurs à la configuration serveur) pour vous aider à devenir un développeur accompli et conscient des enjeux de sécurité. Bon code (sécurisé) à tous !