Cómo proteger tus aplicaciones web de inyecciones SQL
La inyección SQL sigue siendo una de las vulnerabilidades más explotadas en aplicaciones web. Según OWASP, se mantiene consistentemente en el top 10 de riesgos de seguridad. En este artículo vamos a revisar qué es, cómo funciona y cómo proteger tus aplicaciones.
¿Qué es una inyección SQL?
Una inyección SQL ocurre cuando un atacante logra insertar código SQL malicioso a través de los campos de entrada de una aplicación. Si la aplicación no valida ni sanitiza correctamente los datos del usuario, ese código se ejecuta directamente en la base de datos.
Por ejemplo, imagina un formulario de login con este query:
SELECT * FROM users WHERE username = '$username' AND password = '$password';
Si un atacante ingresa ' OR '1'='1 como username, el query resultante seria:
SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '';
Esto devuelve todos los usuarios de la tabla, dando acceso sin credenciales válidas.
Tipos de inyección SQL
1. Inyección clásica (In-band)
El atacante usa el mismo canal para inyectar y obtener resultados. Es la más común y la más fácil de explotar.
2. Inyección ciega (Blind)
La aplicación no muestra errores SQL, pero el atacante puede inferir información basándose en el comportamiento de la aplicación (respuestas true/false o tiempos de respuesta).
3. Inyección fuera de banda (Out-of-band)
Menos común. El atacante usa canales diferentes para inyectar y recibir datos, como DNS o HTTP requests hacia un servidor externo.
Cómo protegerte
Usa prepared statements (consultas parametrizadas)
Esta es la defensa principal. En lugar de concatenar strings, usa parámetros:
// MAL - vulnerable a inyección
$query = "SELECT * FROM users WHERE email = '$email'";
// BIEN - prepared statement
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute(['email' => $email]);
En Laravel, Eloquent y el Query Builder ya usan prepared statements por defecto:
// Eloquent - seguro por defecto
$user = User::where('email', $email)->first();
// Query Builder - también seguro
$user = DB::table('users')->where('email', $email)->first();
Valida y sanitiza los inputs
Nunca confíes en los datos del usuario. Valida tipo, formato y longitud:
$email = filter_var($input, FILTER_VALIDATE_EMAIL);
$id = filter_var($input, FILTER_VALIDATE_INT);
Principio de mínimo privilegio
Tu usuario de base de datos no debería tener permisos de DROP, ALTER o GRANT si solo necesita leer y escribir datos. Crea usuarios con permisos específicos:
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'localhost';
Usa un WAF (Web Application Firewall)
Un WAF puede detectar y bloquear patrones comunes de inyección SQL antes de que lleguen a tu aplicación. No reemplaza las buenas prácticas de código, pero agrega una capa adicional de defensa.
Mantén tus dependencias actualizadas
Los frameworks y ORMs corrigen vulnerabilidades regularmente. Mantén tu stack actualizado y revisa los advisories de seguridad.
Herramientas para testing
- sqlmap: Herramienta open source para detección automática de inyecciones SQL
- OWASP ZAP: Proxy de seguridad para encontrar vulnerabilidades en aplicaciones web
- Burp Suite: Suite completa de testing de seguridad web
Conclusión
La inyección SQL es un problema resuelto a nivel técnico — los frameworks modernos ofrecen protección por defecto. El riesgo aparece cuando escribimos queries manuales sin parametrizar, cuando usamos ORMs de forma insegura, o cuando no validamos los inputs.
La seguridad no es un feature que se agrega al final. Es una práctica que se integra desde el primer commit.