SQL injection: qué es y cómo prevenirla

La SQL injection ocurre cuando una aplicación permite que datos no confiables alteren la estructura de una consulta SQL. El atacante puede cambiar filtros, leer información, modificar registros o ejecutar operaciones adicionales según el motor y los permisos disponibles. La defensa principal es separar siempre el código SQL de los valores.
Cómo nace la vulnerabilidad
Este patrón construye una consulta concatenando entrada externa:
consulta = "SELECT * FROM usuarios WHERE email = '" + email + "'"
cursor.execute(consulta)
Si el valor contiene comillas y operadores SQL, deja de ser solo un email y pasa a formar parte de la sintaxis. Escapar manualmente algunos caracteres no cubre todos los motores, codificaciones y contextos.
Consultas parametrizadas
Usa parámetros proporcionados por el driver:
cursor.execute(
"SELECT id, nombre FROM usuarios WHERE email = %s",
(email,),
)
El marcador exacto depende de la biblioteca. El driver envía o interpreta el valor como dato, no como una parte ejecutable de la consulta. No coloques comillas manuales alrededor del placeholder.
Los ORM también parametrizan valores cuando utilizas sus APIs normales. Las consultas raw, fragmentos literales y filtros construidos dinámicamente siguen requiriendo revisión.
Lo que no se puede parametrizar directamente
Los parámetros suelen representar valores, no nombres de tabla, columnas ni palabras clave. Para un orden dinámico, utiliza una lista permitida:
columnas_permitidas = {"fecha", "importe", "estado"}
if ordenar_por not in columnas_permitidas:
raise ValueError("Columna no permitida")
consulta = f"SELECT id, fecha, importe FROM pedidos ORDER BY {ordenar_por}"
Aquí la interpolación solo ocurre después de comprobar una allow-list cerrada. Aplica la misma idea a ASC/DESC, tablas seleccionables y operadores.
Procedimientos almacenados y SQL dinámico
Un procedimiento no es seguro por el mero hecho de estar en la base de datos. Si concatena parámetros dentro de EXEC o una cadena dinámica, puede reintroducir el riesgo. Usa enlaces de parámetros en el lenguaje del motor y evita ejecutar fragmentos recibidos desde el cliente.
Validación: una capa complementaria
Valida formato, longitud, rango y tipo por motivos de negocio y reducción de superficie. Aun así, una validación no reemplaza la parametrización. Un apellido puede contener apóstrofes legítimos; intentar “limpiarlo” puede dañar datos sin garantizar seguridad.
La codificación o escape específico puede ser una medida de último recurso cuando una API heredada no permite parámetros, pero requiere conocimiento exacto del contexto y no debe ser la estrategia predeterminada.
Mínimo privilegio
La cuenta usada por la aplicación debe tener solo los permisos necesarios. Un servicio de lectura no necesita crear tablas ni administrar usuarios. Separa cuentas por componente y evita que la aplicación se conecte como propietaria del esquema.
El mínimo privilegio limita el impacto si falla otra defensa. Las vistas pueden exponer únicamente columnas permitidas, y los secretos de conexión deben rotarse y almacenarse fuera del código.
Mensajes de error y observabilidad
No devuelvas errores detallados del motor al usuario: pueden revelar tablas, consultas o versiones. Registra internamente un identificador de correlación y la clase de error, pero nunca contraseñas, tokens ni datos sensibles completos.
Monitoriza patrones anómalos, picos de errores y consultas rechazadas. Un firewall de aplicaciones puede ayudar como defensa adicional, pero no corrige una consulta vulnerable.
Pruebas de seguridad
Revisa todos los puntos donde datos externos llegan a consultas: formularios, cabeceras, parámetros de URL, archivos importados, mensajes y valores almacenados previamente. La inyección de segundo orden utiliza datos guardados que más tarde se incorporan de forma insegura.
Añade análisis estático, pruebas de integración y revisión manual. Verifica casos de filtros, búsquedas, paginación y orden dinámico. No pruebes sistemas ajenos sin autorización.
Lista de control para prevenir SQL injection
Parametriza todos los valores; restringe identificadores dinámicos mediante allow-list; evita SQL creado por concatenación; revisa consultas raw y procedimientos; asigna permisos mínimos; oculta errores; protege secretos; y automatiza pruebas.
La corrección debe hacerse en el punto donde se construye la consulta. Bloquear algunos caracteres en la interfaz deja otros canales y futuros cambios expuestos.
Continúa aprendiendo
Amplía este tema con nuestras guías sobre procedimientos almacenados en SQL, restricciones en SQL, transacciones ACID.
Fuentes oficiales
Preguntas frecuentes
¿Qué es una SQL injection?
Es una vulnerabilidad en la que datos no confiables cambian la estructura de una consulta y pueden ejecutar acciones no previstas.
¿Cuál es la defensa principal?
Usar consultas parametrizadas o prepared statements para mantener separados el código SQL y los valores.
¿Un ORM evita siempre la inyección?
Reduce el riesgo en sus APIs normales, pero consultas raw, literales y fragmentos dinámicos todavía pueden ser vulnerables.
¿Se puede parametrizar un nombre de columna?
Normalmente no. Debe seleccionarse desde una lista cerrada de identificadores permitidos.
¿Validar la entrada es suficiente?
No. Es una capa complementaria; la parametrización sigue siendo necesaria aunque el formato se valide.
¿Por qué importa el mínimo privilegio?
Limita las operaciones y datos accesibles si un atacante consigue explotar una consulta.

Deja una respuesta