Entendiendo la inyección SQL

agosto 23, 2024

La inyección SQL es una vulnerabilidad de alto riesgo que afecta principalmente a las aplicaciones web, pero que también pueden estar presentes en el consumo de APIs en aplicaciones móviles. Esta vulnerabilidad le permite a un atacante modificar las consultas que son ejecutadas por el sistema gestor base de datos mediante la inyección de parámetros y da la posibilidad de obtener acceso a información confidencial, modificar o eliminar los registros de la base de datos, en algunos escenarios también permite la carga de archivos maliciosos como una webshell, entre otras actividades malintencionadas que pueden generar una afectación a la integridad, disponibilidad y confidencialidad de la aplicación web o información tratada por la misma.

Esta vulnerabilidad esta contemplada en el OWASP Top 10 dentro de la categoría A03:2021 – Injection y esta categorizada como una de las mas criticas que se puede encontrar en una prueba de penetración.

Existen varios tipos de inyección SQL, entre los principales se encuentran:

  • Inyección SQL basada en errores. Son las que retornan los mensajes de error del sistema gestor base de datos y dan claridad al atacante sobre la malformación de la consulta SQL ejecutada.
  • Inyección SQL a ciegas. Son las que no retornan mensajes de error de forma explicita, pero que mediante su explotación, la aplicación web cambia su comportamiento, lo que permite al atacante deducir o recuperar información de la base de datos.

Para comprender esta vulnerabilidad vamos a trabajar con un laboratorio que esta integrado por una aplicación web vulnerable llamada DVWA, creada en PHP con vulnerabilidades de forma intencional para permitir espacios de practica sin afectar aplicaciones en ambiente productivo y que podrían acarrear repercusiones legales si se realizan sin la respectiva autorización de efectuar pruebas de seguridad. Para el laboratorio se trabajó con una maquina virtual que tiene desplegada la aplicación DVWA, llamada Cap’n Crunch Hacking Web.

Identificando la vulnerabilidad

Desde un navegador se accede a la aplicación DVWA y se inicia sesión en la misma mediante las credenciales admin / password y desde el menú lateral en la opción DVWA Security se pasa al nivel de seguridad bajo.

Posteriormente en la opción SQL Injection del menú lateral verificamos la funcionalidad presentada por el formulario web que pide un identificador de usuario, al probar con números se evidencia que retorna el nombre y apellido de un usuario, además, se determina que la petición realizada por el formulario es tipo GET al enviar el parámetro id en la URL.

Esto nos indica que a nivel de backend la aplicación esta haciendo una consulta a la base de datos por el parámetro id ingresado por el usuario para traer la información mostrada en pantalla, siendo posiblemente una consulta SQL similar a:

SELECT first_name,surname FROM users WHERE id='1';

Siendo esto posible pasamos a probar agregando el carácter especial , lo cual generaría un error de sintaxis debido a que el sistema gestor base de datos estaría esperando otro carácter especial que indique el fin de la cadena de texto en la consulta. Al enviar el carácter especial en el campo del formulario, vemos que la aplicación retorna un mensaje de error, específicamente de sintaxis SQL cerca de , adicionalmente nos indica cual es el sistema gestor base de datos implementado en el servidor, siendo MariaDB.

Comprobando que existe la vulnerabilidad de inyección SQL basada en error sobre el parámetro ID y entendiendo la posible consulta SQL ejecutada en el sistema gestor base de datos, podemos comenzar a manipular esta para obtener información interna de la misma, comenzando por obtener todos los registros de la tabla ingresando en el parámetro id ‘ or ‘1’=’1 obteniendo la consulta SQL:

SELECT first_name,surname FROM users WHERE id='' or '1'='1';

Esto ejecutaría la consulta SQL con el operador lógico OR, si una de las dos condiciones se cumple, y como ‘1’=’1′ siempre es verdadero trae todos los registros de los usuarios.

Identificando el numero de columnas de la consulta SQL

Con el fin de determinar el numero de columnas que esta retornando la consulta SQL, vamos a utilizar el operador algebraico UNION aplicado a base de datos SQL, quedando la consulta a ejecutar de la siguiente manera:

SELECT first_name,surname FROM users WHERE id='1' UNION SELECT 1#';

La anterior consulta SQL únicamente se ejecutara de forma satisfactoria si ambas tablas resultantes tienen el mismo numero de columnas, al inyectar en el parámetro id la expresión 1′ UNION SELECT 1#, estamos tratando de unir el resultado de la consulta SQL original con una tabla de una sola columna que imprime el numero 1, el carácter especial # del final de la consulta permite indicarle al sistema gestor base de datos que el resto de la consulta hace parte de un comentario, por lo cual no es tenido en cuenta para la ejecución de la misma.

Esta ejecución retorna un nuevo error del sistema gestor base de datos, indicando que el numero de columnas no coinciden, por lo tanto el operador UNION no puede ser ejecutado. Vamos a seguir agregando columnas hasta encontrar el numero exacto de columnas que nos permita utilizar el operador UNION, probando ahora la expresión 1′ UNION SELECT 1,2#

Ahora que se ha logrado determinar el numero exacto de columnas, podemos cambiar la consulta para que no únicamente imprima números sino que traiga información relevante, como lo puede ser la versión exacta del sistema gestor base de datos, la base de datos en la que se esta trabajando, el usuario por el cual se esta conectando a la misma, entre otra información, para ello podemos utilizar funciones preestablecidas en el sistema gestor base de datos que ya identificamos previamente como MariaDB, para ello la expresión inyectada en el parámetro id seria 1′ UNION SELECT database(),user()#

Obteniendo las bases de datos

Ahora queremos obtener las bases de datos que tiene definidas el sistema gestor base de datos, para ello nos aprovechamos del conocimiento del esquema de base de datos utilizada por el mismo para su funcionamiento. La expresión a inyectar seria 1′ UNION SELECT 1,schema_name FROM information_schema.schemata#

Identificando que existen dos bases de datos, information_schema que es de uso interno del sistema gestor base de datos y dvwa que es la base de datos a la cual se esta conectando la aplicación web.

Obteniendo las tablas de una base de datos

Si queremos extraer las tablas definidas dentro de esta base de datos es necesario inyectar la expresión 1′ UNION SELECT 1,table_name FROM information_schema.tables WHERE table_schema=’dvwa’#

Identificando de esta manera que existen dos tablas, guestbook y users, generando interés la tabla users.

Obteniendo las columnas de una tabla

Procedemos a averiguar las columnas que componen la tabla users, para ello inyectamos la expresión 1′ UNION SELECT 1,column_name FROM information_schema.columns WHERE table_name=’users’#

Encontrando de esta manera la estructura de la tabla users, identificando las columnas user y password con interés, debido al posible almacenamiento de credenciales de acceso.

Obteniendo registros de una tabla

Para obtener los registros de estos campos de la tabla users inyectamos la expresión 1′ UNION SELECT user,password FROM users#

Obteniendo de esta forma los usuarios y los hashes de las contraseñas de los usuarios registrados en la aplicación web, cabe resaltar que obteniendo los hashes se puede continuar con un proceso de password cracking para recuperar las contraseñas en claro, pero eso será tema de otro post.

De esta manera hemos logrado explicar en detalle y paso a paso en que consiste una vulnerabilidad de inyección SQL basa en error y comprender el riesgo que puede representar tener esta vulnerabilidad en una aplicación web en producción.

Como prevenir inyecciones SQL

Para prevenir las inyecciones SQL en las aplicaciones se puede implementar una o varias de los siguientes controles.

  • Puede evitar la mayoría de los casos de inyección SQL utilizando consultas parametrizadas en lugar de concatenar cadenas dentro de la consulta. Estas consultas parametrizadas también se conocen como “declaraciones preparadas”.
  • Utilice validación de entrada del lado del servidor, permitiendo en preferencia unicamente valores esperados. Esta no es una defensa completa, ya que muchas aplicaciones requieren caracteres especiales, como áreas de texto o API para aplicaciones móviles.
  • Para cualquier consulta dinámica residual, escape los caracteres especiales utilizando la sintaxis de escape específica para ese intérprete. Nota: Las estructuras SQL, como los nombres de tablas, los nombres de columnas, etc., no se pueden escapar y, por lo tanto, los nombres de estructura proporcionados por el usuario son peligrosos. Este es un problema común en el software de redacción de informes.
  • Utilice LIMIT y otros controles SQL dentro de las consultas para evitar la divulgación masiva de registros en caso de inyección SQL.

Referencias