OWASP Top 10 et injections : les requêtes paramétrées

Première leçon du cours Authentification et sécurité web (OWASP), en accès libre : environ 15 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Situer les grandes familles de risques de l'OWASP Top 10 2025
  • Expliquer le mécanisme d'une injection SQL
  • Écrire des requêtes paramétrées et des listes blanches pour les identifiants

OWASP Top 10Injection SQLRequêtes paramétrées

L’OWASP (Open Worldwide Application Security Project) est une fondation qui publie des ressources libres sur la sécurité applicative. Son document le plus connu, le Top 10, classe les risques les plus critiques des applications web. L’édition 2025 comprend :

Rang Catégorie
A01 Contrôle d’accès défaillant (inclut désormais le SSRF)
A02 Mauvaise configuration de sécurité
A03 Défaillances de la chaîne d’approvisionnement logicielle
A04 Défaillances cryptographiques
A05 Injection
A06 Conception non sécurisée
A07 Défaillances d’authentification
A08 Défaillances d’intégrité des logiciels et des données
A09 Défaillances de journalisation et d’alerte
A10 Mauvaise gestion des conditions exceptionnelles

Ce n’est pas une norme complète : c’est une liste de sensibilisation. Pour un audit, on s’appuie plutôt sur l’ASVS (Application Security Verification Standard) de l’OWASP. Dans ce cours, nous allons traiter concrètement plusieurs de ces catégories, en commençant par l’injection.

Le mécanisme d’une injection

Une injection se produit quand une donnée fournie par l’utilisateur est interprétée comme du code. Exemple classique en Node.js :

// DANGEREUX : la saisie est collée dans le texte SQL
const email = req.body.email; // "x' OR '1'='1"
const sql = "SELECT * FROM users WHERE email = '" + email + "'";
// => SELECT * FROM users WHERE email = 'x' OR '1'='1'
// La condition est toujours vraie : toute la table est renvoyée.

L’attaquant a fermé la chaîne avec une apostrophe puis ajouté sa propre logique. Selon la base, il peut aussi lire d’autres tables (UNION SELECT) ou modifier des données.

La solution : la requête paramétrée

Avec une requête paramétrée (ou préparée), on envoie au serveur SQL d’un côté la structure, de l’autre les valeurs. La valeur ne peut jamais devenir du SQL, quoi qu’elle contienne.

// Avec le pilote PostgreSQL "pg" : placeholders $1, $2...
const { rows } = await pool.query(
  "SELECT id, email FROM users WHERE email = $1",
  [email] // valeur transmise séparément
);

// Avec le module natif node:sqlite (Node 22.5+) ou MySQL : placeholders "?"
const stmt = db.prepare("SELECT id, email FROM users WHERE email = ?");
const user = stmt.get(email);

Les ORM (Prisma, Sequelize, Drizzle…) paramètrent automatiquement, sauf si vous utilisez leurs fonctions de requête brute avec concaténation.

Le piège des identifiants

Les placeholders ne fonctionnent que pour des valeurs. Un nom de colonne ou un sens de tri (ORDER BY price DESC) ne peut pas être paramétré. On utilise alors une liste blanche :

const COLONNES_TRI = new Set(["price", "name", "created_at"]);
function clauseTri(col, sens) {
  if (!COLONNES_TRI.has(col)) throw new Error("Colonne de tri invalide");
  const dir = sens === "desc" ? "DESC" : "ASC"; // deux valeurs possibles seulement
  return `ORDER BY ${col} ${dir}`;
}

À retenir

  • Ne jamais construire du SQL (ni une commande shell) par concaténation de données externes.
  • Remplacer « échapper les apostrophes » par des requêtes paramétrées.
  • Valider par liste blanche tout ce qui ne peut pas être paramétré.
  • Le même principe vaut pour les autres injections : child_process.execFile avec un tableau d’arguments plutôt que exec avec une chaîne.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 1JavaScript dans le navigateur2 indices

    Construire une requête paramétrée

    Écrivez findUserByEmail(email) qui ne concatène jamais l’email dans le SQL et renvoie un objet { text, values } (format du pilote pg) :

    • text vaut exactement "SELECT id, email FROM users WHERE email = $1" ;
    • values est un tableau contenant l’email.

    Si email n’est pas une chaîne, lancez une TypeError.

    function findUserByEmail(email) {
      // À compléter
    }
  2. CodeDifficulté 2JavaScript dans le navigateur2 indices

    Liste blanche pour ORDER BY

    Écrivez buildOrderBy(column, direction) :

    • colonnes autorisées : name, price, created_at ; sinon lancez une Error ;
    • direction vaut "desc" (insensible à la casse) pour DESC, toute autre valeur donne ASC ;
    • renvoie par exemple "ORDER BY price DESC".
    function buildOrderBy(column, direction) {
      // À compléter
    }
  3. QCMDifficulté 11 indice

    Identifier la bonne défense

    Quelle pratique protège efficacement une requête SQL contre l’injection ?

    • Remplacer les apostrophes par deux apostrophes
    • Utiliser une requête paramétrée avec placeholders
    • Limiter la longueur du champ à 50 caractères
    • Encoder la saisie en Base64 avant de la concaténer

Quiz de fin de leçon

  1. Que signifie l’injection dans l’OWASP Top 10 ?

    • Un serveur qui injecte des publicités
    • Une donnée externe interprétée comme du code par un interpréteur
    • Un paquet npm malveillant
    • Un mot de passe trop court
  2. Peut-on passer un nom de colonne en paramètre $1 ?

    • Oui, toujours
    • Non : les placeholders ne servent qu'aux valeurs, il faut une liste blanche
    • Oui, mais seulement avec MySQL
  3. Quelle fonction Node limite le risque d’injection de commande ?

    • child_process.exec avec une chaîne
    • child_process.execFile avec un tableau d'arguments
    • eval

La suite du cours

  1. XSS : échapper le HTML et poser une CSP15 min
  2. Stocker les mots de passe : hachage avec scrypt18 min
  3. Sessions, jetons signés et cookies sécurisés18 min
  4. Contrôle d'accès, en-têtes de sécurité et gestion des erreurs18 min

Authentification et sécurité web (OWASP) · Avancé · 3 h 24