Skip to main content
Sql Injection tipos ejemplos y contramedidas

Sql Injection tipos ejemplos y contramedidas

Una inyección SQL es una vulnerabilidad de seguridad en la que se permite que un atacante interfiera con las consultas que una aplicación realiza en su base de datos.

Bienvenidos a un nuevo post en ByteMind. En el caso de hoy les explicaremos qué es SQL Injection, qué tipos hay de SQL Injection, así como algunos ejemplos comunes, la explicación de cómo encontrar y explotar una vulnerabilidad de este tipo y ciertas contramedidas ante este tipo de ataques.

Pero como primer paso habrá que conocer en que consiste este ataque.

 

¿Qué es SQL Injection?

Una inyección SQL (sqli) es una vulnerabilidad de seguridad en la que se permite que un atacante interfiera con las consultas que una aplicación realiza en su base de datos. En general, permite que un atacante vea ciertos datos que no debería de poder recuperar. Esto puede incluir datos pertenecientes a otros usuarios, accesos, estructura de tablas o cualquier otro dato al que la aplicación pueda acceder. En muchos de los casos, un atacante también puede modificar o eliminar dichos datos, provocando cambios persistentes en el contenido o en el comportamiento de la aplicación.

En algunas situaciones, también es posible que un atacante pueda escalar un ataque de inyección SQL para comprometer el servidor subyacente, otra infraestructura de fondo, o realizar ataques de denegación de servicio.

Sql Injection tipos ejemplos y contramedidas

 

¿Qué impacto puede tener este tipo de ataques?

Un ataque exitoso de inyección SQL puede resultar en un acceso no autorizado a datos sensibles como pueden ser contraseñas de acceso, detalles de tarjetas de crédito o información personal del usuario. En los últimos años, muchas de las violaciones de datos, data leaks, etc… han sido el resultado de una correcta ejecución de ataques de este tipo, lo que ha acabado provocando daños graves a la reputación de las empresas y multas regulatorias por la exposición de los datos.

En algunos casos, un atacante podría obtener un backdoor persistente en los sistemas de una organización, lo que llevaría a un compromiso a largo plazo que puede pasar desapercibido durante un período prolongado en el tiempo.

 

Tipos de ataques SQL Injection

Principalmente podemos destacar 3 tipos de ataques:

  • Ataque por error -> es el ataque más común y el más fácil de explotar ya que es la propia aplicación la que va indicando los errores de la base de datos al realizar las diferentes consultas. Con este error es muy sencillo obtener cualquier dato de la base de datos ya sean estructura, tablas, campos e incluso los datos almacenados.
  • Ataque por union -> este tipo de ataque consiste en que el portal devuelva un resultado, y a partir de ahí, añadir al resultado original el resultado de otra query de tal forma que se muestren junto con los datos del portal, los datos sensibles del mismo que no debería de poderse obtener.
  • Ataque ciego (blind) -> es el ataque más complicado y el más avanzado, es la última opción cuando ninguno de los ataques anteriores funcionan. En este caso hay que ser muy creativos y se deben de realizar preguntas a la base de datos mediante booleanos, es decir, verdadero o falso, todo aquello que se necesite saber. Aquí podemos separar en dos tipos más:
    • Basado en condicionales -> si la consulta está bien mostrará los resultados, sino no mostrará nada.
    • Basada en tiempo -> si la consulta es correcta devolverá los resultados a los n segundos, si no no mostrará nada.

 

Ejemplos de Inyección SQL

Con respecto a las inyecciones SQL hay una gran variedad de vulnerabilidades, ataques y técnicas que surgen en diferentes situaciones.

En este caso vamos a ver los siguientes ejemplos:

  • Obtener información de la base de datos
  • Recuperación de datos ocultos
  • Subvertir la lógica de la aplicación
  • Ataques UNION
  • Blind SQL Injection (inyección ciega)

A continuación procedemos a explicar cada uno de los ejemplos mencionados.

 

Obtener información de la base de datos

En la gran mayoría de ocasiones, por no decir siempre, es necesario recopilar información sobre la base de datos. Esto incluye información como el tipo y versión del software y el contenido de la base de datos en lo que se refiere a esquemas, tablas y columnas existentes.

 

Consultar el tipo y versión de la base de datos

Las diferentes bases de datos proporcionan distintas formas de consultar su versión, a menudo es necesario probar distintas consultas con el fin de detectar una que funcione y con ello, descubrir el software que hay por debajo.

Dependiendo del software para determinar la versión se podrían utilizar las siguientes consultas:

Software Consulta
MySQL, Microsoft SQL Server SELECT @@version
Oracle SELECT * FROM v$version
PostgreSQL SELECT version()

Por ejemplo, en caso de realizar un ataque UNION contra una base de datos MySQL la consulta podría ser la siguiente:

Esto nos puede devolver un resultado como el siguiente, confirmando que la base de datos es un MySQL, o MariaDB como es el caso de este ejemplo:

 

Listar el contenido de la base de datos

La mayoría de bases de datos (a excepción de Oracle) tienen un conjunto de vistas llamado esquema de información que proporciona información sobre la misma.

En el caso de mysql, se puede consultar mediante information_schema.tables para enumerar las tablas existentes, vamos a verlo con un ejemplo. La consulta sería de la siguiente forma:

Y devolvería un resultado similar al siguiente:

La salida anterior indica que las tablas existentes, así como el esquema al que pertenecen. Posteriormente se puede consultar las columnas existentes en cada una de las tablas quedando el comando de la siguiente manera:

Esto devolvería una salida similar a la siguiente:

Este resultado mostraría las columnas existentes en la tabla especificada así como el tipo de datos de cada una de las columnas.

 

Recuperación de datos ocultos

Para el ejemplo vamos a considerar una aplicación de venta de productos que muestra los mismos en diferentes categorías.

Cuando el usuario hace click, por ejemplo, en la categoría teclados, su navegador solicitará la siguiente URL:

Esta url haría que la aplicación realizase la siguiente consulta SQL para recuperar los productos de dicha categoría:

La restricción published = 1 se utiliza para mostrar sólo los productos que se encuentran publicados a la venta y los que no se encuentran tendían el valor de 0.

En este ejemplo la aplicación no implementa ninguna defensa contra este tipo de ataques por lo que se puede construir un ataque modificando el parámetro de la url y quedando de la siguiente forma:

Lo que provocaría que la consulta SQL fuese la siguiente:

Con el indicador de comentarios — le indicamos que el resto de la consulta se interprete como un comentario y, por lo tanto, no ejecutará el resto de la misma por lo que nos mostraría todos los productos de dicha categoría independientemente de si está publicada su venta o no.

Si intentamos ir más lejos, podemos mostrar todos los productos independientemente de la categoría, para ello vamos a hacer una ejecución simple quedando la url así:

Y nuestra consulta SQL quedaría de la siguiente forma:

La consulta modificada devolverá todos los elementos donde la categoría sea keyboards o, donde 1 sea igual a 1. Como 1=1 siempre es verdadero, la consulta devolverá todos los elementos.

 

Subvertir la lógica de la aplicación

Para el ejemplo vamos a considerar una aplicación que permita a los usuarios iniciar sesión con un nombre de usuario y una contraseña.

Si el usuario envía el nombre usertest y la contraseña userpass, la aplicación verificará las creadenciales con la siguiente consulta SQL:

Si la consulta devuelve los detalles de un usuario, el acceso es correcto, de lo contrario, este es rechazado.

Aquí, un atacante podría iniciar sesión como cualquier otro usuario sin la necesidad de incluir una contraseña utilizando simplemente la secuencia de comentarios de SQL — para eliminar la verificación de la contraseña en la cláusula WHERE de la consulta.

Vamos a verlo con un ejemplo, en el cual enviamos el nombre de usuario admin’– y una contraseña en blanco, quedando la consulta de la siguiente forma:

Esta consulta devolvería al usuario cuyo nombre es admin e iniciaría con éxito la sesión del atacante con el mismo.

 

Ataques UNION

Cuando una aplicación es vulnerable a la inyección SQL y los resultados de la consulta se devuelven dentro de las respuestas de la propia aplicación la palabra clave UNION puede utilizarse para recuperar datos de otras tablas existentes en la base de datos.

La palabra clave UNION permite ejecutar una o más consultas adicionales y mostrar los resultados junto a la consulta original, vamos a verlo con un ejemplo:

La consulta anterior nos devolverá un único conjunto de resultados con dos columnas que contendrían los valores de column1 y column2 de la tabla table1 y las columnas column3 y column4 de la tabla table2.

Para que este tipo de consultas funcionen se deben de cumplir dos requisitos:

  • Las consultas individuales deben devolver el mismo número de columnas
  • Los tipos de datos en cada una de las columnas deben ser compatibles entre las columnas individuales

Estos requisitos implican conocer previamente:

  • El número de columnas que devuelve la consulta original
  • El tipo de dato que devuelve cada una de las columnas originales con el fin de obtener los resultados de la consulta inyectada

A continuación se explican estos dos últimos puntos.

 

Determinar el número de columnas

Para detectar el número de columnas que devuelve la consulta original hay dos métodos efectivos.

El primer método consiste en inyectar una serie de cláusulas ORDER BY e incrementar el índice de columna hasta que se produzca un error. Por ejemplo:

Estas cargas modifican la consulta original para ordenar los resultados en diferentes columnas. La cláusula se puede especificar por su índice por lo que no es necesario conocer el nombre de cada una de las columnas.

Cuando este índice exceda el número de columnas existentes mostrará un error similar al siguiente:

En este caso obtenemos el error en el número 4 por lo que detectamos que existen 3 columnas en la tabla. Pero, puede darse el caso en el que el error aparezca en su respuesta HTTP, podría devolver un error genérico que no mostrase información relevante o, simplemente, podría no devolver ningún error, pero tampoco ningún resultado, por lo que podría dar indicios de este mismo resultado.

El segundo método consiste en enviar una serie de cargas útiles que especifiquen un número de valores nulos, por ejemplo:

Si el número de nulos no corresponde con el número de columnas devolverá un error con el cual podríamos detectar de cuantas columnas existen en la tabla.

El uso de NULL se realiza porque los tipos de datos de cada columna deben de ser compatibles entre las consultas original y las inyectadas. Dado que NULL es convertible a cualquier tipo de dato de uso común aumenta las posibilidades de obtener el recuento de columnas existentes.

 

Encontrar columnas con un tipo de dato útil

Una vez que se conoce el número de columnas que existen en la tabla, para comprobar el tipo se debe de realizar la prueba con cada una de las columnas para probar el tipo de dato que contienen las mismas, por ejemplo:

En este caso introducimos un string con el caracter ‘s’ en cada una de las columnas, si el tipo de dato no coincide nos devolverá un error similar al siguiente:

Pero si el tipo de dato coincide nos devolverá los resultados, por lo que habríamos descubierto el tipo de dato que almacena esa columna.

 

Blind SQL Injection

La inyección SQL ciega (Blind SQLi) surge cuando una aplicación es vulnerable a la inyección SQL pero sus respuestas HTTP no contienen los resultados de la consulta relevante ni los detalles de ningún error de la base de datos.

En este tipo de vulnerabilidades muchas técnicas como el ataque por cláusula UNION no son efectivas, ya que dependen de los resultados obtenidos de la consulta inyectada. Aunque esto, no quiere decir que no sea posible explotar dicha vulnerabilidad.

Para este tipo de vulnerabilidad se pueden utilizar técnicas de condicionales o de retraso y que explicamos a continuación, aunque debido a la dificultad y el tiempo requerido, es recomendable utilizar herramientas automatizadas como podría ser sqlmap.

 

Desencadenando respuestas condicionales

Vamos a considerar una aplicación que utiliza cookies para hacer un seguimiento y recopilar información sobre su eso. Las solicitudes llevarán un encabezado similar al siguiente:

Cuando TrackId procesa una solicitud que contiene la cookie, la aplicación determinará si se trata de un usuario conocido con una sentencia SQL similar a la siguiente:

Esta consulta es vulnerable a la inyección SQL, pero la misma no devolverá resultados al usuario. Sin embargo la aplicación se comportará de forma diferente dependiendo de si devuelve o no datos.

Por ejemplo, si la consulta anterior devuelve datos, en pantalla se mostrará el mensaje “Bienvenido”, mientras que si no devuelve datos no mostrará ningún mensaje en pantalla.

Este comportamiento sería suficiente para verificar que existe la vulnerabilidad y recuperar información activando diferentes respuestas condicionales.

Por ejemplo realizamos las  siguientes consultas:

El primero de los valores, 1=1, siempre es correcto por lo que la condición inyectada será verdadera y mostrará el mensaje “Bienvenido”, por el contrario el segundo valor devolverá falso, por lo que no se mostrará ningún mensaje.

Por ejemplo, vamos a suponer que hay una tabla llamada users que contiene los campos username y password y que existe un usuario llamado admin.

Comenzaremos lanzando la siguiente consulta:

La consulta anterior devuelve el mensaje “Bienvenido” por lo que conocemos que el primer carácter del campo password es superior a la letra m.

Posteriormente, lanzamos la siguiente consulta:

Esta consulta no devuelve ningún mensaje por lo que conocemos que el primer carácter se encuentra entre la letra m y la letra p.

Lanzamos una tercera consulta:

En este caso hemos descubierto que el carácter ‘o’ nos devuelve el mensaje “Bienvenido” por lo que ya hemos conocido el primer carácter del campo password.

De esta forma podemos conocer el valor completo del campo password, aunque eso sí, el tiempo necesario para completar un ataque es muy superior al de cualquier otra técnica de inyección.

 

Desencadenar errores de SQL mediante respuestas condicionales

Utilizando el mismo ejemplo anterior, hay ocasiones en el que una inyección no muestra ningún cambio en la página, haciendo mucho más laborioso llevar a cabo este tipo de ataques.

En esta situación, a menudo, es posible inducir a la aplicación a devolver respuestas activando errores de SQL. Esto implicará modificar la consulta para que cause un error en la base de datos si la condición es verdadera, pero no si la condición es falsa, ayudando a detectar los mismos y obtener algo de información.

Para ello vamos a verlo con un ejemplo:

En las anteriores consultas, en el primer caso se evaluará como 1/0 por lo que devolverá un error dándonos información del resultado, mientras que en la segunda evaluará NULL por lo que no dará ningún error en la aplicación.

Con esta técnica, ahora podemos recuperar los datos de la misma forma que lo hicimos en el anterior ejemplo, con una consulta similar a la siguiente:

 

Desencadenando retrasos

En el caso de que ninguna de las opciones anteriores nos de algún resultado positivo de la vulnerabilidad, otra de las técnicas utilizadas es la activación de retardos de tiempo condicionalmente. Debido a que las consultas, generalmente, son procesadas de forma sincrona por la aplicación, retrasar la ejecución también retrasará la respuesta por lo que puede darnos información al respecto.

En el caso de MySQL podríamos activar un retardo de la siguiente forma:

Con esta técnica podemos recuperar estas entradas al activar un retraso, por lo que si se activa el retraso sabremos que la condición es verdadera.

De esta forma podríamos añadir nuestra inyección de la siguiente forma:

Y de esta forma ir obteniendo información al respecto.

 

Contramedidas

La mayoría de las vulnerabilidades de inyección SQL se deben a una incorrecta concatenación de los datos, por ejemplo en el siguiente código no se comprueba la entrada introducida:

El método correcto sería el uso de sentencias preparadas y consultas parametrizadas, que dependerán del lenguage de programación y de la librería utilizada, además de un saneamiento del texto introducido, por ejemplo mediante expresiones regulares que limiten estos caracteres, la verificación del tipo de dato introducido, etc.

Para ello la gran mayoría de librerías en los diferentes lenguages existentes ya proporcionan funciones creadas para este fin, haciéndo más fácil implementar estos métodos de protección.

 

 

Hasta aquí es todo por ahora. Espero les haya sido de utilidad en su día a día o en sus desarrollos y como siempre, cualquier duda, aporte o cometario es bienvenida.

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada.