SQLi - Injection SQL : faire dire à votre base de données ce qu'elle ne devrait pas
L'injection SQL se produit lorsque des données contrôlées par l'utilisateur sont insérées directement dans une requête SQL, modifiant sa structure et sa logique. La base de données exécute alors ce que l'attaquant a écrit, pas ce que le développeur avait prévu. C'est l'une des vulnérabilités les plus anciennes, et pourtant toujours l'une des plus fréquentes.
Comment ça se produit
Voici une requête d'authentification typique construite par concaténation de chaînes : le schéma classique du code vulnérable :
Code vulnérable : concaténation directe
// Node.js
const query = `SELECT * FROM users
WHERE email='${email}'
AND password='${password}'`;
// PHP
$query = "SELECT * FROM users WHERE email='" . $email . "' AND password='" . $password . "'";
// Python
query = "SELECT * FROM users WHERE email='" + email + "' AND password='" + password + "'"
Avec des entrées normales (email : [email protected], password : monmotdepasse), la requête est correcte. Maintenant avec une entrée malveillante :
Contournement d'authentification
email: [email protected]' --
password: n'importe quoi
-- Requête résultante (le -- commente le reste) :
SELECT * FROM users WHERE email='[email protected]' --' AND password='n'importe quoi'
-- Ce que la base de données exécute réellement :
SELECT * FROM users WHERE email='[email protected]'
Le -- est le commentaire SQL dans la plupart des SGBD. Tout ce qui suit est ignoré. La vérification du mot de passe est supprimée. La requête retourne l'utilisateur admin, la connexion réussit.
Les variantes d'exploitation
UNION-based : extraction de données : Si la réponse de l'application affiche le résultat de la requête, l'attaquant peut utiliser UNION SELECT pour extraire n'importe quelle table de la base.
Extraction de la table users via UNION
-- Injection dans un paramètre de recherche
id=1 UNION SELECT username, password_hash, email FROM users--
-- L'application affiche maintenant les données de la table users
-- à la place du résultat normal
-- Pour connaître les tables disponibles (MySQL) :
id=1 UNION SELECT table_name, column_name, null FROM information_schema.columns--
Blind SQLi : inférence sans résultat visible : Si l'application n'affiche pas les résultats de la requête (mais se comporte différemment selon la réponse), l'attaquant peut extraire les données bit par bit via des questions booléennes ou des délais.
Blind SQLi : extraction caractère par caractère
-- Si l'application retourne 200 quand la condition est vraie, 404 quand elle est fausse
-- L'attaquant découvre le mot de passe caractère par caractère :
id=1 AND SUBSTRING(password_hash,1,1)='a'-- → 200 (vrai)
id=1 AND SUBSTRING(password_hash,1,1)='b'-- → 404 (faux)
...
-- Time-based : si l'application n'a même pas de comportement différent
-- On mesure le temps de réponse (MySQL) :
id=1 AND IF(SUBSTRING(password_hash,1,1)='a', SLEEP(3), 0)--
Injection dans d'autres contextes : L'injection SQL ne se limite pas aux formulaires de connexion. Elle peut toucher les paramètres d'URL, les headers HTTP, les cookies, les payloads JSON ou XML envoyés à une API, partout où une valeur est intégrée dans une requête SQL.
L'impact réel
- Contournement d'authentification : accès admin sans identifiants valides
- Exfiltration de données : extraction complète des tables : utilisateurs, mots de passe, données clients
- Modification et suppression de données :
UPDATE,DELETEinjectés - Exécution de commandes système : sur certains SGBD mal configurés (SQL Server avec
xp_cmdshell, MySQL avecINTO OUTFILE)
Une injection SQL dans une application non protégée, c'est souvent l'équivalent d'un accès root à la base de données, depuis l'internet.
Remédiation
1. Requêtes paramétrées (prepared statements) : C'est la seule protection fondamentale. La requête et les données sont transmises séparément au moteur SQL, qui n'interprète jamais les données comme du code. Il est structurellement impossible pour une valeur utilisateur de modifier la logique de la requête.
Requêtes paramétrées : Node.js (mysql2)
// mysql2
const [rows] = await db.execute(
'SELECT * FROM users WHERE email = ? AND password_hash = ?',
[email, passwordHash]
);
// pg (PostgreSQL)
const result = await db.query(
'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
[email, passwordHash]
);
Requêtes paramétrées : Python
# psycopg2 (PostgreSQL)
cursor.execute(
"SELECT * FROM users WHERE email = %s AND password_hash = %s",
(email, password_hash)
)
# sqlite3
cursor.execute(
"SELECT * FROM users WHERE email = ? AND password_hash = ?",
(email, password_hash)
)
Requêtes paramétrées : PHP (PDO)
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND password_hash = :hash');
$stmt->execute(['email' => $email, 'hash' => $passwordHash]);
$user = $stmt->fetch();
2. ORM avec requêtes sécurisées : Les ORM modernes (Sequelize, Prisma, SQLAlchemy, Hibernate, ActiveRecord) génèrent des requêtes paramétrées par défaut. Attention aux méthodes qui permettent d'injecter du SQL brut (Sequelize.literal(), db.raw(), Query.filter() avec chaîne brute) : elles annulent la protection.
Attention aux raw queries même avec un ORM
// Dangereux - même avec Prisma ou Sequelize
const users = await prisma.$queryRaw(`SELECT * FROM users WHERE email = '${email}'`);
// Correct - paramètre sécurisé
const users = await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;
3. Principe de moindre privilège sur le compte base de données : Le compte applicatif ne devrait avoir accès qu'aux tables dont il a besoin, uniquement en lecture/écriture selon les besoins. Si l'application ne fait que lire, pas besoin du droit DROP TABLE.
Récapitulatif des protections
- Toujours utiliser des requêtes paramétrées, sans exception, même pour des valeurs qui semblent sûres
- Auditer tous les usages de concaténation de chaînes dans les requêtes SQL
- Si un ORM est utilisé, identifier et protéger les raw queries
- Appliquer le principe de moindre privilège sur le compte base de données de l'application
- Ne jamais afficher les erreurs SQL en production : elles donnent des informations précieuses à l'attaquant
Pour aller plus loin
Une question après la lecture ?
Des doutes sur la façon dont votre application construit ses requêtes SQL ? Envoyez un message.
Ou réservez directement un cadrage de 30 minutes
Réserver un appel →