La cybersécurité des applications web est un enjeu crucial dans un monde où les données numériques sont devenues un actif majeur. Parmi les menaces les plus subtiles, les failles de type « time-based blind SQL injection » se distinguent par leur capacité à exfiltrer des données sans déclencher d’alertes évidentes. Ces attaques exploitent les temps de réponse des bases de données pour reconstruire des informations sensibles, une technique qui demande expertise et discrétion. Cet article explore en détail ce type d’attaque, en s’appuyant sur un cas réel observé lors d’un test d’intrusion.
En quoi une « Time-Based Blind SQL Injection » est-elle différente d’une injection SQL classique ?
Une attaque de type SQL injection aveugle repose sur l’exploitation de requêtes SQL dans des paramètres d’application vulnérables. Contrairement à une injection SQL classique qui affiche directement des résultats (comme des erreurs ou des données exfiltrées), une attaque « blind » (aveugle) repose sur des indices subtils, comme le temps de réponse du serveur.
Dans une variante « time-based », un attaquant insère des commandes SQL capables de « mettre en pause » la base de données pour vérifier si une condition est vraie ou fausse. Cette technique permet d’extraire des données caractère par caractère en modifiant les délais de réponse, sans interaction visible avec l’utilisateur.
Étude de cas : Une faille dans une application basée sur PostgreSQL
Lors d’un pentest sur une application web, une vulnérabilité de ce type a été identifiée. Voici comment cette faille a été exploitée.
1. Détection de la vulnérabilité
Une requête de test a été envoyée sur une API de l’application. Une réponse HTTP 200 (OK) indiquait une requête réussie. En revanche, l’ajout d’une apostrophe (‘) a généré une erreur HTTP 500, indiquant une faille potentielle d’injection SQL. L’erreur a disparu lorsque deux apostrophes ont été ajoutées ( »), confirmant une possible vulnérabilité dans la gestion des entrées utilisateur.
Requête de recherche classique dans le paramètre ‘text’ :
On ajoute un apostrophe à notre texte, qui génère un code d’erreur HTTP 500 :
On ajoute un deuxième apostrophe pour voir si l’erreur disparaît, et c’est le cas avec un code de réponse HTTP 200 OK :
2. Mise en place d’une injection « time-based »
Pour tester si une commande SQL pouvait influencer les temps de réponse, une requête utilisant la fonction pg_sleep() de PostgreSQL a été envoyée. Résultat : le serveur a pris plusieurs secondes pour répondre, confirmant la possibilité d’influencer les délais (cf. en bas à droite du screenshot). Plusieurs spécificités sont à noter pour pouvoir effectuer un sleep :
- La base de données est PostgreSQL, on utilise donc la fonction « pg_sleep() »
- Les espaces, sous quelconque forme (« + », « %20 »…), ne sont pas acceptés par le serveur, il a donc fallu trouver un autre stratagème. Un commentaire peut être utilisé à la place : « /**/ »
- Nous avons utilisé la concaténation de string pour pouvoir exécuter notre requête, via « || »
https://<url>/api/v1/products?text=a'||((select/**/pg_sleep(10)))||'
3. Exploitation conditionnelle
Super, on a pu faire dormir la base de données ! L’objectif était ensuite d’exfiltrer des données en fonction de conditions vérifiées au sein des requêtes SQL :
- Si le résultat est « true », alors nous souhaitons un temps de réponse de 3 secondes.
Payload :'||(SELECT/**/CASE/**/WHEN/**/(1=1)/**/THEN/**/pg_sleep(3)/**/END)||'
- Si le résultat est « false », alors nous souhaitons une réponse immédiate.
Payload :'||(SELECT/**/CASE/**/WHEN/**/(1=0)/**/THEN/**/pg_sleep(3)/**/END)||'
En utilisant cette approche, il est possible d’extraire les caractères de manière itérative, en vérifiant un à un les éléments de la base de données.
4. Automatisation avec SQLMap
L’outil SQLMap n’a pas détecté cette faille automatiquement. Un payload personnalisé a été ajouté dans le fichier de configuration pour inclure des particularités spécifiques :
- Absence d’espaces standards, remplacés par des commentaires (/**/)
- Gestion des chaînes avec concaténation (||).
Pour ce faire, on l’ajoute dans le fichier SQLMAP approprié aux injections « blind » : /usr/share/sqlmap/data/xml/payloads/time_blind.xml.
Il est important de noter que l’on a enlevé les « ‘ » de début et de fin de payload. C’est obligatoire sinon SQLMAP les interprète et encode tous les caractères du payload, ce que l’on ne veut pas. Certaines variables spécifiques à SQLMAP sont également inclues telles que :
- [INFERENCE], pour indiquer à SQLMAP où effectuer ses requêtes avancées, si le payload seul n’est pas suffisant
- [SLEEPTIME], utilisant la valeur de SQLMAP par défaut, ou spécifiée dans « –time-sec »
- [RANDNUM], permettant à SQLMAP de choisir des valeurs numériques pour ses tests
Une fois le payload ajouté. Il faut lancer SQLMAP avec les paramètres nécessaires. Voici la commande utilisée :
sqlmap.py --level=5 --risk=3 -u "https://<url>/api/v1/products?text=asdf*" -H "Cookie: XSRF-TOKEN= […snip…] " -H "Origin: https://<url>" -H "Referer: https:// <url>" --dbms=postgresql --random-agent --tamper=space2comment.py --skip-urlencode --technique=T --skip-waf --delay 0.2 --current-db --prefix="'" --suffix="'" --time-sec=3
Explication :
- « –level=5 –risk=3 » : Utiliser le plus haut niveau de risque. Ce n’est pas un problème ici puisque nous sommes sur une requête GET et ne pouvons donc altérer la base de données à cause de mauvaises manipulations de SQLMAP
- « -u » et « -H » : spécifier l’URL et les en-têtes nécessaires. Utilisation de « * » dans l’URL pour spécifier à SQLMAP où injecter
- « –dbms=postgresql –random-agent » : Indiquer la base de données utilisées et on spécifie l’utilisation d’un user-agent aléatoire ne contenant pas la string « sqlmap »
- « –tamper=space2comment.py –skip-urlencode –technique=T » : Indiquer à SQLMAP de remplacer les espaces par « /**/ », de ne pas encoder les caractères et de n’utiliser que ses payloads issues des techniques basées sur le temps de réponse
- « –skip-waf –delay 0.2 –current-db –time-sec=3 » : Indiquer de ne pas tester si un WAF (Web Application Firewall) est en place, d’envoyer maximum 5 requêtes par seconde car il y avait une limite imposée par le serveur, de tenter un sleep de 3 secondes. Enfin, si une injection est identifiée, tenter de récupérer le nom de la base.
- « –prefix= »‘ » –suffix= »‘ » » : Permet d’ajouter les « ‘ » en début et fin de payload car on n’avait pas pu les ajouter directement dans le fichier
On voit dans l’image ci-dessus que SQLMAP a détecté l’injection grâce à notre payload (« a' ||(SELECT CASE WHEN (8238=8238) THEN PG_SLEEP(3) END)||'
»). Le nom de base de données récupéré est « public ». Ensuite on récupère le nom des tables en ajoutant le paramètre « –tables » au lieu de « –current-db » :
sqlmap.py --level=5 --risk=3 -u "https://<url>/api/v1/products?text=asdf*" -H "Cookie: XSRF-TOKEN= […snip…] " -H "Origin: https://<url>" -H "Referer: https://<url>" --dbms=postgresql --random-agent --tamper=space2comment.py --skip-urlencode --technique=T --skip-waf --delay 0.2 --current-db --prefix="'" --suffix="'" --time-sec=3 --tables
On peut donc récupérer l’ensemble de la base de données et pour des raisons de confidentialité évidentes, nous ne partageons pas le contenu de la base. Voici les étapes logiques qui suivent :
- Récupérer les noms de colonnes avec : « -T [NomTable] –columns »
- Récupérer les données de la colonne voulue : « -T [NomTable] -C [NomColonne] –dump »
Comment se protéger contre ce type d’attaque ?
Les failles de type SQL injection aveugle peuvent être évitées grâce à une combinaison de bonnes pratiques de développement et de configurations sécurisées.
1. Paramétrage sécurisé des requêtes SQL
- Utiliser des requêtes préparées : Empêcher les entrées utilisateur d’être interprétées comme des commandes SQL.
- Validation stricte des données : Limiter les caractères acceptés et les types d’entrée.
2. Mise en place de protections au niveau applicatif
- Pare-feu d’applications web (WAF) : Bloquer les patterns d’attaque courants tels que pg_sleep() ou CASE.
- Encodage des entrées : Convertir les caractères spéciaux pour qu’ils soient traités comme des données brutes.
3. Tests réguliers de sécurité
- Pentests périodiques : Identifier les vulnérabilités dans les paramètres utilisateur.
Pour conclure, la faille « time-based blind SQL injection » illustre à quel point une application mal sécurisée peut devenir une porte d’entrée pour des attaquants déterminés. Ce type de vulnérabilité, bien que discret, peut conduire à des pertes majeures de données. En appliquant les bonnes pratiques de développement, en utilisant des outils de détection avancés et en sensibilisant les équipes, il est possible de réduire considérablement ces risques.
Pour aller plus loin, assurez-vous que vos applications soient régulièrement auditées et testées contre les dernières menaces : Si vous souhaitez effectuer une analyse approfondie de vos systèmes, contactez-nous !