Skip to Content
SécuritéPartie 6 — Injections6.1 — Le principe des injections

Chapitre 6.1 — Le principe des injections

⏱️ TL;DR — Toutes les injections ont le même mécanisme : un interpréteur (SQL, shell, LDAP, template…) reçoit une chaîne qui mélange du code que tu contrôles et des données que l’utilisateur contrôle, et il ne peut pas distinguer les deux — alors il exécute ce que l’utilisateur a glissé. La cause quasi unique : construire la commande par concaténation ("... " + entrée + " ..."). La parade quasi unique : séparer le code des données — fournir le code avec des emplacements (paramètres), puis passer les données à part, pour que l’interpréteur les traite comme de pures valeurs, jamais comme du code. Un seul principe éclaire SQLi, command injection, NoSQL, LDAP.

🎯 Objectifs

  • Comprendre la distinction code / données et pourquoi les mélanger crée l’injection.
  • Voir le mécanisme commun sur un exemple SQL minimal.
  • Comprendre pourquoi la concaténation est le geste fatal, et le paramétrage la parade.
  • Généraliser : le même principe vaut pour tous les interpréteurs.

Code et données, mélangés par erreur

Un interpréteur (le moteur SQL, le shell, un annuaire LDAP) reçoit une chaîne de caractères et la comprend : il y voit des mots-clés (du code : SELECT, WHERE, ;, OR) et des valeurs (des données : 'lea@ecole.fr'). Le problème naît quand tu construis cette chaîne en collant une entrée utilisateur au milieu de ton code : l’interpréteur reçoit un seul texte et n’a aucun moyen de savoir quelle partie était « ton code » et quelle partie était « la donnée de l’utilisateur ». Si l’utilisateur a glissé des caractères de code dans sa donnée, l’interpréteur les exécute.

C’est exactement comme si tu dictais une phrase à quelqu’un et qu’au milieu la personne dictée ajoutait « … et maintenant, ignore tout et fais ceci » : sans marquage clair entre « ce que je dicte » et « instruction », l’ambiguïté est exploitable.

Le mécanisme sur un exemple SQL

Prenons une recherche d’utilisateur par email, construite par concaténation :

// ❌ VULNERABLE : l'entree est COLLEE dans le code SQL $email = $_GET['email']; $sql = "SELECT * FROM users WHERE email = '$email'"; // usage normal : email = lea@ecole.fr // -> SELECT * FROM users WHERE email = 'lea@ecole.fr' (ok)

Maintenant, un attaquant fournit comme « email » la valeur ' OR '1'='1 :

-- La chaine construite devient : SELECT * FROM users WHERE email = '' OR '1'='1' -- '1'='1' est TOUJOURS vrai -> la requete renvoie TOUS les utilisateurs

L’apostrophe de l’attaquant a fermé la chaîne de données, et le OR '1'='1' qui suit est interprété comme du code SQL. La donnée s’est transformée en logique. Pire, avec '; DROP TABLE users; --, l’attaquant peut enchaîner une seconde commande destructrice. C’est ça, une injection SQL — et le schéma est identique pour toutes les injections : une donnée qui devient du code parce que rien ne les séparait.

🎯 Côté attaquant — Devant un champ, un paramètre d’URL, un en-tête, l’attaquant teste des caractères de code de l’interpréteur visé : une apostrophe ' (SQL), un point-virgule ; ou un pipe | (shell), des opérateurs $gt/$ne (NoSQL). Si la réponse change de façon révélatrice (erreur SQL, résultats anormaux, comportement inattendu), c’est le signe que sa donnée atteint un interpréteur sans séparation — et il affine jusqu’à contrôler la commande. Le premier symptôme d’une injection, c’est souvent un message d’erreur trop bavard sur une simple apostrophe.

La parade universelle : séparer code et données

Puisque le problème est le mélange, la solution est la séparation. Au lieu de construire une chaîne mêlant code et données, tu fournis à l’interpréteur :

  1. le code, avec des emplacements (placeholders) là où iront les valeurs ;
  2. les données, séparément, associées à ces emplacements.

L’interpréteur compile d’abord la structure de la commande (le code), puis insère les valeurs dans les emplacements en les traitant comme de pures données — sans jamais les réinterpréter comme du code. C’est le paramétrage (requêtes préparées côté SQL) :

// ✅ SUR : code et donnee separes (requete preparee) $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$_GET['email']]); // l'email, meme s'il vaut ' OR '1'='1, est traite comme une SIMPLE VALEUR // -> il cherche un utilisateur dont l'email est LITTERALEMENT " ' OR '1'='1 " // -> aucune injection possible

La différence est structurelle, pas cosmétique : ce n’est pas qu’on « échappe mieux » l’apostrophe, c’est que l’apostrophe de l’attaquant ne peut plus changer la structure de la requête — elle est enfermée dans le rôle de « valeur ». C’est pour ça que le paramétrage est la parade de référence, bien supérieure à l’échappement manuel (qu’on oublie, qu’on fait mal, qui varie selon l’interpréteur).

💡 Réflexe — Le déclencheur d’alarme : toute chaîne de commande construite avec une variable d’entrée par concaténation ("SELECT ... " . $x, un template string SQL avec ${x}, "ls " + chemin pour un shell). Dès que tu vois ça, la question est : « puis-je paramétrer à la place ? ». Réponse presque toujours oui. Le +/./${} d’une entrée dans une commande est le motif de l’injection.

⚠️ Piège — « J’échappe les apostrophes, donc c’est bon. » L’échappement manuel est fragile : on oublie un champ, on se trompe de fonction, l’encodage diffère, un contexte particulier passe à travers (nombres non quotés, LIKE, encodages multi-octets). Le paramétrage est robuste par construction — il n’y a rien à échapper car code et données ne se rencontrent jamais dans la même chaîne. Préfère toujours paramétrer ; l’échappement n’est qu’un dernier recours pour les rares cas non paramétrables (chapitre 6.2).

Un principe, toutes les injections

Le même schéma « donnée mélangée au code → exécutée » se décline sur chaque interpréteur, avec la même parade « séparer / paramétrer » :

InterpréteurInjectionSéparation (parade)
Base SQLSQL injectionRequêtes préparées / ORM
Shell systèmeCommand injectionAPI qui passe les args en tableau (pas de shell)
Base NoSQL (Mongo…)NoSQL injectionRequêtes typées, valider que l’entrée est une chaîne, pas un opérateur
Annuaire LDAPLDAP injectionAPI paramétrée / échappement LDAP dédié
Moteur de templatesSSTI / template injectionNe jamais rendre une entrée comme template

C’est la beauté de ce chapitre : un mécanisme à comprendre, une parade à appliquer partout. Les chapitres suivants déclinent SQL (6.2) puis les autres (6.3), mais tu connais déjà le fond.

🧭 Sur FormaCampus — Sur FormaCampus, aucune requête n’est construite par concaténation d’entrée : côté API Symfony, tout passe par Doctrine (paramétré par défaut) ou par des requêtes préparées ; côté Moodle, par l’API $DB paramétrée. La règle d’équipe est explicite dans les conventions : « une variable d’entrée dans une commande = paramètre, jamais de concaténation ». Les rares cas non paramétrables (un tri dynamique ORDER BY) passent par une allow-list (chapitre 6.2). Cette discipline unique ferme d’un coup SQLi et ses cousines.

✏️ Exercices

Exercice 1 — Explique l’exploitation. Soit "SELECT * FROM users WHERE nom = '" . $_GET['nom'] . "'". Un attaquant passe nom = ' OR 1=1 -- . Écris la requête résultante et explique ce qu’elle fait.

✅ Solution

La requête devient : SELECT * FROM users WHERE nom = '' OR 1=1 -- '. L’apostrophe de l’attaquant ferme la chaîne de données, OR 1=1 (toujours vrai) fait que la clause WHERE sélectionne toutes les lignes, et -- commente le reste de la requête (l’apostrophe finale du code d’origine), évitant une erreur de syntaxe. Résultat : la requête renvoie tous les utilisateurs (contournement du filtre). La donnée ' OR 1=1 -- a été interprétée comme du code SQL parce qu’elle était concaténée. Parade : requête préparée avec un paramètre pour nom.

Exercice 2 — Paramètre-le. Réécris ce code Node (pilote pg) de façon sûre : client.query(`SELECT * FROM cours WHERE titre = '${titre}'`).

✅ Solution

Utiliser un paramètre au lieu du template string concaténé : client.query('SELECT * FROM cours WHERE titre = $1', [titre]). Le $1 est un emplacement ; titre est passé séparément dans le tableau et traité comme une pure valeur, quel que soit son contenu (apostrophes, OR, ;…). La structure de la requête est fixée avant l’insertion de la donnée : aucune injection possible. (Le ${titre} dans un backtick JS est précisément le motif à bannir.)

🧠 Quiz de révision

1. Quel est le mécanisme commun à toutes les injections ?

Une donnée non fiable est mélangée à du code dans une même chaîne envoyée à un interpréteur, qui ne peut pas distinguer les deux et exécute les caractères de code glissés par l’utilisateur. Donnée → devient code, faute de séparation.

2. Quel geste est presque toujours la cause d’une injection ?

La concaténation d’une entrée utilisateur dans une chaîne de commande ("... " . $entree, `... ${entree}`, "cmd " + arg). C’est ce mélange code+données dans un seul texte qui rend l’injection possible.

3. En quoi le paramétrage diffère-t-il de l’échappement ?

Le paramétrage sépare structurellement code et données : le code (avec des emplacements) est compilé d’abord, puis les données sont insérées comme de pures valeurs, jamais réinterprétées. L’échappement garde tout dans une même chaîne et « neutralise » les caractères — fragile, oubliable, dépendant du contexte. Le paramétrage est robuste par construction.

4. Pourquoi une donnée paramétrée ne peut-elle pas devenir du code ?

Parce que la structure de la commande est fixée avant l’insertion de la valeur : la valeur est enfermée dans le rôle de « donnée » (un emplacement), et l’interpréteur ne la ré-analyse pas comme du code. Une apostrophe ou un OR dans la valeur reste un caractère littéral de la donnée.

5. Le principe « séparer code et données » vaut-il seulement pour SQL ?

Non : il vaut pour tous les interpréteurs — shell (command injection), NoSQL, LDAP, moteurs de templates. Un seul mécanisme (donnée mélangée au code) et une seule parade (séparer/paramétrer/valider) couvrent toute la famille des injections.


Chapitre suivant : SQLi & requêtes préparées — l’injection reine en détail, et comment la fermer avec PDO et Doctrine (y compris les cas retors comme le tri dynamique).

Last updated on