Guía de inyección SQL: 8 prácticas recomendadas para prevenir ataques de inyección SQL
26 de marzo de 2021
0 minutos de lecturaLa inyección SQL es una de las vulnerabilidades más peligrosas para las aplicaciones en línea. Se produce cuando un usuario agrega datos no confiables a una consulta de base de datos, por ejemplo, al completar un formulario web. Si es posible realizar una inyección SQL, los atacantes pueden crear entradas para robar datos valiosos, eludir la autenticación o corromper los registros de tu base de datos.
Hay distintos tipos de ataques de inyección SQL, pero, en general, todos tienen una causa similar: los datos no confiables que ingresa el usuario se concatenan con la cadena de consulta. Por lo tanto, la entrada del usuario puede alterar la intención original de la consulta.
Algunos ejemplos de inyección SQL son:
Agregar un valor booleano a una cláusula WHERE que siempre sea verdadero, como
' OR 1=1Anular parte de la consulta al ingresar comentarios de línea
--Finalizar la consulta inicial e iniciar una nueva:
'; DROP TABLE USERS;Conectar datos de varias tablas mediante
UNION
En esta guía, explicaré ocho prácticas recomendadas que cualquier programador de aplicaciones puede aplicar para prevenir ataques de inyección SQL. Empecemos a proteger tu aplicación contra la inyección SQL.

No confíes en la validación de entradas del lado del cliente
Usa un usuario de base de datos con privilegios restringidos
Usa sentencias preparadas y parametrización de consultas
Analiza tu código en busca de vulnerabilidades de inyección SQL
Usa una capa ORM
No confíes en las listas de bloqueo
Valida las entradas
Ten cuidado con los procedimientos almacenados
1. No confíes en la validación de entradas del lado del cliente.
La validación de entradas del lado del cliente es muy útil. Permite evitar que los datos no válidos lleguen a la lógica de tu sistema. Sin embargo, esto solo funciona con usuarios que no tienen malas intenciones y quieren usar el sistema según lo previsto. Darle al usuario comentarios directos cuando un valor no es válido es muy útil y mejora la experiencia. Por eso, conviene usar la validación del lado del cliente para mejorar la experiencia de usuario.
En el caso de la inyección SQL, no es un método en el que debas confiar. Puedes eliminar la validación del lado del cliente modificando el código JavaScript que se carga en tu navegador. Además, es bastante fácil hacer una llamada HTTP básica al backend en una arquitectura cliente-servidor con un parámetro que provoque una inyección SQL. Puedes hacerlo con herramientas como Postman o con comandos tradicionales de curl.
Debes validar los datos en el servidor, idealmente lo más cerca posible del origen. En este caso, donde creas la consulta SQL. Debes considerar potencialmente dañino todo lo que te envíe un cliente. Por lo tanto, confiar en la validación del lado del cliente para prevenir la inyección SQL es una pésima idea.
2. Usa un usuario de base de datos con privilegios restringidos
Como mencionamos antes, hay distintos tipos de ataques de inyección SQL. Algunos son más dañinos que otros. Por ejemplo, supongamos que mi consulta SQL es algo como "SELECT * FROM USER WHERE USERID = '" + userid +"'". La inyección " foo' OR '1'='1 " devolverá todos los usuarios, lo que ya es dañino. Sin embargo, " '; UPDATE message SET password = 'EVIL" causará aún más problemas, porque el intruso habrá cambiado todas las entradas.
Al crear un usuario de base de datos para tu aplicación, debes tener en cuenta sus privilegios. ¿La aplicación necesita poder leer, escribir y actualizar todas las bases de datos? ¿También necesita truncar o eliminar tablas? Si limitas los privilegios de tu aplicación en la base de datos, puedes minimizar el impacto de una inyección SQL. Probablemente sea mejor no usar un solo usuario de base de datos para la aplicación, sino crear varios y asociarlos con roles específicos de la aplicación. Los problemas de seguridad suelen ser el resultado de una cadena de eventos, así que debes prestar atención a cada eslabón para evitar daños graves.
3. Usa sentencias preparadas y parametrización de consultas
Muchos lenguajes incluyen funciones que ayudan a prevenir la inyección SQL. Al escribir consultas SQL, puedes usar una sentencia preparada para compilarlas. Con una sentencia preparada, podemos parametrizar las consultas. La parametrización de consultas es una técnica para crear sentencias SQL de forma dinámica. Creas una consulta base con algunos marcadores de posición y adjuntas de manera segura los parámetros que proporciona el usuario.
Al usar sentencias preparadas reales y consultas parametrizadas, la propia base de datos se encarga de escapar los datos. Primero, crea el plan de ejecución de la consulta a partir de la cadena de consulta con marcadores de posición. En el segundo paso, los parámetros (no confiables) se envían a la base de datos. El plan de consulta ya está creado, así que los parámetros ya no pueden influir en él. Esto evita por completo la inyección.
Ejemplo en Java:
Ejemplo en Python con el conector de MySQL:
Ejemplo en JavaScript con mysql2:
Hay varias formas de hacerlo en JavaScript, por ejemplo, con una base de datos MySQL. Ten en cuenta que, al usar .query(), no ejecutas una sentencia preparada real. En este caso, la sustitución de parámetros se realiza del lado del cliente, por lo que estás simulando una sentencia preparada. Para ejecutar una sentencia preparada real en la base de datos, debes usar la función .execute().
4. Analiza tu código en busca de vulnerabilidades de inyección SQL
Escribir código personalizado puede ser fácil, pero también es fácil cometer errores. Para revisar tu código, quizá tengas procesos como revisiones de código y programación en pareja. Sin embargo, ¿la persona que revisa tu código o programa contigo tiene conocimientos de seguridad? ¿Puede detectar un error de inyección SQL en tu código? En cualquier caso, sería útil analizar automáticamente tu código personalizado para detectar posibles vulnerabilidades de seguridad, como la inyección SQL. Con una herramienta SAST (pruebas estáticas de seguridad de aplicaciones) como Snyk Code, puedes inspeccionar automáticamente tu código en busca de vulnerabilidades de seguridad, como la inyección SQL. Por ejemplo, puedes automatizar fácilmente este proceso en tu ciclo de vida de desarrollo de software (SDLC) al conectar tu repositorio de Git con Snyk.

5. Usa una capa ORM
También puedes considerar usar una capa de mapeo objeto-relacional (ORM). Una capa ORM transforma los datos de la base de datos en objetos y viceversa. El uso de una biblioteca ORM reduce las consultas SQL explícitas y, por lo tanto, disminuye considerablemente la vulnerabilidad a la inyección SQL.
Algunos ejemplos excelentes de bibliotecas ORM son Hibernate para Java y Entity Framework para C#. La naturaleza fuertemente tipada de estos lenguajes suele permitir generar el mapeo entre los objetos y las tablas de la base de datos. De esta manera, ni siquiera tienes que escribir consultas SQL.
Aun así, el problema puede surgir si necesitas crear consultas personalizadas. Hibernate para Java tiene su propio lenguaje de consultas, Hibernate Query Language (HQL). Al compilar consultas HQL, debes volver a tener en cuenta la inyección y usar la función createQuery(), que funciona de manera similar a una sentencia preparada.
Para JavaScript, también hay bibliotecas ORM conocidas, como sequelize. Con sequelize, puedes definir cómo se asignan los valores a tipos específicos en la base de datos. Pero seamos realistas: al final, una biblioteca ORM debe traducir la lógica a sentencias SQL. Por eso, confiamos en que estas bibliotecas implementen correctamente el escape de los parámetros.
Para asegurarte de que tu biblioteca ORM no tenga problemas de inyección SQL, debes analizarla en busca de vulnerabilidades conocidas. Usar una versión incorrecta o desactualizada de sequelize o hibernate puede causarte problemas. Usa Snyk Open Source para revisar tu proyecto y evitar vulnerabilidades de inyección SQL ocultas en tus bibliotecas, además de muchos otros problemas.
6. No confíes en las listas de bloqueo
Probablemente muchas personas ya lo sepan, pero lo diré una vez más: no implementes listas de bloqueo para tus parámetros. Este enfoque crea un conjunto de reglas que definen las entradas vulnerables. Si una entrada cumple estas reglas, se bloquea la solicitud. Sin embargo, si las reglas son demasiado permisivas, una entrada maliciosa podría funcionar. Si son demasiado estrictas, bloquearán entradas válidas.
Por ejemplo, bloqueamos todas las solicitudes que contienen la palabra OR. Puede parecer una regla razonable, pero resulta que “Or” es un nombre de pila israelí muy común. Eso significa que a varios de mis compañeros de trabajo se les bloquearía la solicitud al ingresar sus nombres. Lo mismo sucede con el apóstrofo '. Hay innumerables nombres que contienen ese carácter. Piensa en O’Neill y O'Donnell, pero también en nombres como Dont’a, por ejemplo.
7. Valida las entradas
¡Sí, siempre debes validar las entradas! Aunque las sentencias preparadas con parametrización de consultas son la mejor defensa contra la inyección SQL, siempre debes crear varias capas de protección. Al igual que limitar los privilegios de un usuario de base de datos, la validación de entradas es una buena práctica para reducir el riesgo general de tu aplicación. Además, hay situaciones en las que no se pueden usar sentencias preparadas. Algunos lenguajes no admiten este mecanismo o los sistemas de bases de datos más antiguos no permiten proporcionar las entradas del usuario como parámetros. En esos casos, la validación de entradas es una alternativa aceptable.
Asegúrate de que la validación de entradas se base en listas de permitidos y no en listas de bloqueo, como explicamos antes. Crea una regla que describa todos los patrones permitidos, por ejemplo, con una expresión regular, o usa una biblioteca bien mantenida. Combínala con sentencias preparadas y parametrización de consultas para obtener una defensa sólida.
8. Ten cuidado con los procedimientos almacenados
Muchas personas creen que usar procedimientos almacenados es una buena forma de prevenir la inyección SQL. No siempre es así. Al igual que las consultas SQL que se crean en tu aplicación, los procedimientos almacenados también pueden ser objeto de ataques de inyección.
Al igual que con las consultas SQL de tu aplicación, debes parametrizar las consultas de tus procedimientos almacenados en lugar de concatenar los parámetros. Es bastante fácil prevenir la inyección SQL en un procedimiento almacenado.
Así que no hagas esto en MySQL:
En su lugar, usa consultas parametrizadas en tus procedimientos almacenados:
Como se muestra arriba, las mismas reglas que se aplican al código de tu aplicación también se aplican a los procedimientos almacenados. La implementación de estos procedimientos varía según la base de datos. Asegúrate de saber cómo implementarlos en tu base de datos y ten cuidado también con las inyecciones. Aunque creo que sería mejor tener toda la lógica en tu aplicación, un procedimiento almacenado puede ser una solución razonable si el lenguaje que usas para desarrollar no admite sentencias preparadas.


