Inyección SQL: cómo funciona y cómo detenerla
La inyección SQL ha sido la principal vulnerabilidad web durante décadas. Descubre exactamente cómo funciona un ataque y aprende las defensas que la eliminan.
Tabla de contenidos
Por qué sigue existiendo la inyección SQL
La inyección SQL se documentó por primera vez en 1998. Ha aparecido en todas las listas OWASP Top 10 desde su creación. Ha sido responsable de algunas de las mayores brechas de datos de la historia: TalkTalk (4 millones de registros), Heartland Payment Systems (130 millones de números de tarjeta) e innumerables casos más.
La razón por la que persiste no es la complejidad. La inyección SQL es fácil de entender y, lo que es crucial, fácil de prevenir. Persiste porque los desarrolladores concatenan la entrada del usuario en las consultas SQL sin pensar en las consecuencias, a menudo bajo presión de tiempo y sin formación en seguridad.
Este tutorial muestra exactamente cómo funciona y las técnicas concretas que la eliminan.
Cómo funciona normalmente una consulta SQL
Considera un formulario de inicio de sesión. La aplicación toma un nombre de usuario y una contraseña y los comprueba contra la base de datos:
SELECT * FROM users WHERE username = 'alice' AND password = 'secretpass';
Si la consulta devuelve una fila, el usuario queda autenticado. Este es el comportamiento normal y esperado.
La vulnerabilidad surge cuando un desarrollador construye esta consulta concatenando cadenas directamente a partir de la entrada del usuario:
# Código Python vulnerable
query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "';"
La suposición es que los usuarios proporcionarán valores normales. Los atacantes no lo hacen.
El ataque: escapar de la cadena
SQL usa comillas simples para delimitar los valores de tipo cadena. Si un atacante proporciona un nombre de usuario que contiene una comilla simple, puede escapar de la cadena prevista e inyectar su propio SQL.
Entrada del atacante:
- Nombre de usuario:
' OR '1'='1 - Contraseña:
anything
Consulta resultante:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = 'anything';
Como OR '1'='1' es siempre verdadero, esta consulta devuelve todos los usuarios. El inicio de sesión tiene éxito para el primer usuario de la base de datos, normalmente un administrador.
Saltarse por completo la comprobación de la contraseña:
- Nombre de usuario:
admin'-- - El
--es un comentario SQL; todo lo que le sigue se ignora
SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything';
-- Se convierte en:
SELECT * FROM users WHERE username = 'admin';
Se omite la autenticación. El campo de contraseña nunca se comprueba.
Más allá de la autenticación: extraer datos
La inyección SQL no se limita a saltarse la autenticación. Los ataques más graves extraen datos de toda la base de datos.
Inyección basada en UNION
El operador SQL UNION combina los resultados de dos sentencias SELECT. Un atacante puede usarlo para recuperar datos de otras tablas:
-- Consulta original
SELECT name, description FROM products WHERE id = 1;
-- Inyectada
SELECT name, description FROM products WHERE id = 1
UNION SELECT username, password FROM users--;
Esto añade todos los nombres de usuario y contraseñas a los resultados de productos devueltos al atacante.
Inyección SQL a ciegas
Muchas aplicaciones no muestran a los usuarios los errores de la base de datos ni los resultados de las consultas. La inyección SQL a ciegas extrae datos bit a bit haciendo preguntas de verdadero/falso:
-- ¿Es el primer carácter de la contraseña del administrador una 'a'?
SELECT * FROM users WHERE id = 1 AND SUBSTRING(password, 1, 1) = 'a';
Si la página se comporta de forma diferente cuando la condición es verdadera frente a cuando es falsa, el atacante puede inferir la respuesta. Herramientas automatizadas como sqlmap pueden extraer bases de datos enteras mediante inyección a ciegas en cuestión de minutos.
Consultas fuera de banda y apiladas
En algunos sistemas de bases de datos, los atacantes pueden:
- Ejecutar múltiples sentencias (consultas apiladas): eliminar tablas, crear cuentas de puerta trasera
- Leer archivos del sistema de archivos del servidor (
LOAD_FILE()en MySQL) - Escribir archivos en el servidor (creando potencialmente una web shell)
- Desencadenar conexiones de red para exfiltrar datos fuera de banda
En el peor de los casos, la inyección SQL significa un compromiso completo del servidor, no solo la exposición de datos.
Las defensas que realmente funcionan
Defensa 1: consultas parametrizadas (sentencias preparadas)
Esta es la defensa principal y elimina la inyección SQL, no simplemente la reduce.
Con una consulta parametrizada, el código SQL y los datos se envían a la base de datos por separado. La base de datos compila primero la plantilla SQL y luego enlaza los valores proporcionados por el usuario. Los valores nunca pueden interpretarse como SQL: siempre se tratan como datos.
Python con consulta parametrizada:
# Seguro
cursor.execute(
"SELECT * FROM users WHERE username = %s AND password = %s",
(username, password)
)
Java con PreparedStatement:
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE username = ? AND password = ?"
);
stmt.setString(1, username);
stmt.setString(2, password);
Sin importar lo que el usuario introduzca en el campo username —incluido ' OR '1'='1—, se trata como un valor de cadena literal, no como SQL. La inyección es imposible.
Defensa 2: ORM y constructores de consultas
Los mapeadores objeto-relacional (ORM) como SQLAlchemy, Hibernate, ActiveRecord y Django ORM usan consultas parametrizadas internamente. Usar un ORM correctamente previene la inyección SQL en casi todos los casos:
# Django ORM — seguro
User.objects.filter(username=username, password=password)
Precaución: la mayoría de los ORM tienen vías de escape para SQL sin procesar (raw(), execute()) que reintroducen el riesgo de inyección si se concatena la entrada del usuario. Trata los métodos de SQL sin procesar con el mismo cuidado que la construcción manual de consultas.
Defensa 3: procedimientos almacenados
Los procedimientos almacenados pueden ser seguros si se implementan correctamente: deben usar entradas parametrizadas internamente. Un procedimiento almacenado que concatena cadenas internamente sigue siendo vulnerable.
Defensa 4: principio de privilegio mínimo
La cuenta de base de datos que usa la aplicación web debería tener solo los permisos que necesita:
- Una aplicación con muchas lecturas solo necesita
SELECT - Ninguna aplicación debería ejecutarse como
root,saoDBA - Distintos componentes de la aplicación pueden usar distintas cuentas de base de datos con distintos permisos
Esto limita el impacto de una inyección exitosa: un atacante que solo puede hacer SELECT no puede eliminar tablas ni escribir archivos.
Defensa 5: validación de entrada
Valida que las entradas coincidan con su formato esperado:
- Un ID de usuario debería ser un entero positivo: rechaza cualquier otra cosa antes de que llegue a la consulta
- El nombre de un producto no debería contener palabras clave de SQL: usa una lista de permitidos con los caracteres esperados
La validación de entrada es una medida de defensa en profundidad, no un control principal. Reduce la superficie de ataque, pero no debería ser la única protección.
Defensa 6: WAF (cortafuegos de aplicaciones web)
Un WAF puede detectar y bloquear los patrones comunes de inyección SQL. Es una capa adicional, no un sustituto de las consultas parametrizadas. Los WAF pueden eludirse con trucos de ofuscación y codificación. No confíes en ellos como tu única defensa.
Pruebas de inyección SQL
Nociones básicas de pruebas manuales:
- Introduce una comilla simple (
') en cada campo de entrada: los mensajes de error de la base de datos a menudo revelan los puntos de inyección - Prueba
1=1y1=2en los parámetros numéricos y compara las respuestas - Prueba
'; SELECT SLEEP(5);--: si la respuesta se retrasa, hay una inyección a ciegas
Herramientas automatizadas:
- sqlmap: la herramienta estándar para la detección y explotación (solo contra sistemas que estés autorizado a probar)
- Burp Suite: intercepta y manipula solicitudes; tiene un escáner de inyección SQL en la edición Pro
- OWASP ZAP: escáner gratuito con detección de inyección SQL
Lista de verificación de remediación
- Reemplaza toda la concatenación de cadenas en las consultas de base de datos por consultas parametrizadas
- Audita todas las llamadas de SQL sin procesar en el código ORM
- Aplica cuentas de base de datos con privilegio mínimo
- Activa la supresión de errores de la base de datos en producción (los errores exponen información del esquema)
- Revisa y prueba los procedimientos almacenados
- Añade reglas de WAF como capa adicional
- Ejecuta sqlmap o su equivalente contra el entorno de pruebas antes de cada lanzamiento
La inyección SQL tiene una solución completa y bien comprendida. No hay razón para que aparezca en código nuevo. La única barrera es la concienciación y la disciplina durante el desarrollo.