L'injection SQL (ou SQLi) figure depuis plus de vingt ans parmi les failles web les plus répandues, et elle continue de faire des ravages. Elle occupe une place de choix dans le Top 10 de l'OWASP sous la catégorie « Injection ». Pourtant, son principe est simple à comprendre — et, bonne nouvelle, simple à corriger une fois qu'on en saisit le mécanisme.

Dans ce guide, tu vas découvrir comment une injection SQL se produit, observer un exemple pédagogique de contournement d'authentification, puis consacrer la seconde moitié de ta lecture à ce qui compte vraiment : te défendre efficacement.

#3OWASP Top 10catégorie Injection
~25 ansd'existenceet toujours exploitée
1/3des applis web testéesprésentent une injection

Cadre légal. Tester une injection SQL sur un site dont tu n'es pas propriétaire et sans autorisation écrite est un délit puni par la loi (en France, articles 323-1 et suivants du Code pénal). Tout ce que tu lis ici est destiné à comprendre et à défendre. Pour t'entraîner légalement, utilise les labs de Lockbay Academy, conçus exactement pour ça.

1. D'où vient le problème ?

Une application web stocke presque toujours ses données dans une base de données relationnelle (MySQL, PostgreSQL, SQLite…) qu'elle interroge via le langage SQL. Le souci apparaît quand le développeur construit ses requêtes en collant directement des données saisies par l'utilisateur dans la chaîne SQL.

Le serveur ne distingue alors plus ce qui relève de l'instruction (le code SQL) de ce qui relève de la donnée (ce que tape l'utilisateur). Un attaquant exploite cette confusion en glissant des fragments de SQL dans un champ texte. C'est exactement le même type de faille que le XSS, mais appliqué au langage de la base de données plutôt qu'au navigateur.

2. Anatomie d'une requête vulnérable

Imagine un formulaire de connexion. Côté serveur, un développeur pressé écrit quelque chose comme ceci (exemple en PHP, mais le problème est identique dans tous les langages) :

// CODE VULNÉRABLE — ne jamais faire ça
$user = $_POST['username'];
$pass = $_POST['password'];

$sql = "SELECT * FROM users
        WHERE username = '$user' AND password = '$pass'";

$result = $db->query($sql);

Si l'utilisateur saisit alice et secret123, la requête envoyée à la base devient parfaitement normale :

SELECT * FROM users
WHERE username = 'alice' AND password = 'secret123';

Le problème : les guillemets simples qui délimitent la donnée sont eux-mêmes saisissables par l'utilisateur. Rien n'empêche d'en injecter un.

3. Exemple : contournement de connexion

C'est l'illustration la plus célèbre de la SQLi. Dans le champ username, un attaquant saisit la chaîne suivante (le mot de passe peut rester vide) :

' OR 1=1 --

La requête réellement exécutée par la base devient alors :

SELECT * FROM users
WHERE username = '' OR 1=1 --' AND password = '';

Décortiquons ce qui se passe :

  • Le premier ' ferme prématurément la chaîne du nom d'utilisateur.
  • OR 1=1 ajoute une condition toujours vraie : la clause WHERE retourne donc toutes les lignes de la table.
  • -- démarre un commentaire SQL, ce qui annule tout le reste de la requête, y compris la vérification du mot de passe.

Résultat : la base renvoie le premier utilisateur (souvent l'administrateur), et l'application, qui se contente de vérifier « au moins une ligne trouvée ? », valide la connexion. L'attaquant entre sans connaître aucun mot de passe.

Au-delà de ce cas d'école, la même faille permet, selon le contexte, de lire des tables entières via des requêtes UNION, d'extraire silencieusement des données par injection « aveugle » (blind SQLi), voire d'exécuter des commandes système sur des configurations mal verrouillées. Les conséquences vont de la fuite massive de données personnelles à la prise de contrôle complète du serveur.

4. La défense : couper le problème à la racine

La bonne nouvelle, c'est que l'injection SQL est l'une des failles les mieux comprises et les plus faciles à éliminer définitivement. Voici les remparts, du plus fondamental au plus complémentaire.

Saisie malveillante
' OR 1=1 --
→
Concaténation SQL
→
Données exfiltrées
Même saisie
→
Requête préparée
→
Paramètre neutralisé

4.1 Les requêtes préparées (la solution numéro un)

La parade décisive consiste à séparer le code SQL des données. Avec une requête préparée (prepared statement), tu envoies d'abord la structure de la requête avec des emplacements réservés (? ou :param), puis tu transmets les valeurs séparément. Le moteur ne les interprète plus jamais comme du SQL :

// CODE SÉCURISÉ — requête préparée (PHP / PDO)
$stmt = $db->prepare(
    "SELECT * FROM users WHERE username = ? AND password = ?"
);
$stmt->execute([$user, $pass]);

Avec cette approche, la saisie ' OR 1=1 -- est traitée comme une simple chaîne de caractères : la base cherche littéralement un utilisateur dont le nom est ' OR 1=1 --, ne le trouve pas, et refuse la connexion. L'attaque est neutralisée par construction. C'est la règle d'or : ne concatène jamais une donnée utilisateur dans une requête SQL.

4.2 Les ORM, des garde-fous par défaut

Les ORM (Doctrine, Prisma, Hibernate, Eloquent, le module SQLAlchemy de Python…) génèrent des requêtes paramétrées automatiquement. Tu manipules des objets, ils produisent le SQL sécurisé. Attention toutefois : la plupart des ORM offrent une porte de sortie « SQL brut » (raw query). Dès que tu l'utilises, tu redeviens responsable de paramétrer toi-même tes valeurs.

4.3 La validation et le typage des entrées

En complément — et jamais en remplacement — des requêtes préparées, valide systématiquement ce que tu reçois. Si un paramètre est censé être un entier (un identifiant de produit, par exemple), force le typage et rejette tout le reste :

// On attend un identifiant numérique : on l'impose
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($id === false) {
    http_response_code(400);
    exit('Identifiant invalide');
}

Privilégie toujours une logique d'allow-list (n'autoriser que ce qui est explicitement attendu) plutôt qu'une deny-list (tenter de bloquer les caractères dangereux), car les listes de blocage sont presque toujours contournables.

4.4 Le principe du moindre privilège côté base

Même une faille résiduelle fait beaucoup moins de dégâts si le compte utilisé par l'application n'a que les droits strictement nécessaires. L'application web ne doit jamais se connecter à la base avec un compte administrateur (root, postgres). Crée un compte dédié, limité aux opérations utiles :

-- Compte applicatif minimal
CREATE USER 'app_web'@'localhost' IDENTIFIED BY '...';
GRANT SELECT, INSERT, UPDATE, DELETE
  ON boutique.* TO 'app_web'@'localhost';
-- Pas de DROP, pas de GRANT, pas d'accès aux autres bases

Ainsi, même si une injection passe, l'attaquant ne pourra ni supprimer des tables, ni lire les bases voisines, ni créer de nouveaux comptes.

4.5 Le WAF : une défense en profondeur

Un pare-feu applicatif (WAF) comme ModSecurity ou les protections de Cloudflare détecte et bloque les motifs d'attaque connus avant qu'ils n'atteignent ton code. C'est utile, surtout pour gagner du temps face aux scans automatisés, mais il ne faut jamais s'en remettre à lui seul : un WAF se contourne, alors qu'une requête préparée ferme la porte définitivement. Vois-le comme une serrure supplémentaire, pas comme le mur porteur.

5. À retenir

  • L'injection SQL naît de la confusion entre code et donnée.
  • Les requêtes préparées (ou un ORM bien utilisé) suppriment le problème à la source : c'est ton réflexe prioritaire.
  • Validation des entrées, moindre privilège DB et WAF forment une défense en profondeur qui limite l'impact d'une erreur.
  • Une seule règle à graver : ne concatène jamais une saisie utilisateur dans une requête SQL.

Tu veux voir une injection se produire en direct, puis la corriger de tes propres mains ? C'est précisément ce que proposent les labs SQLi de Lockbay Academy : un environnement par élève, légal et reproductible, où tu attaques pour mieux comprendre, puis tu défends pour de vrai.