Posted in

No todo es «select * from» – NoSQL Injection (Parte 1) – Fundamentos

En esta serie de entradas analizaremos en detalle el ataque de inyección NoSQL, empleando diferentes técnicas que nos permitirán comprometer la información contenida en una base de datos no-relacional.

En esta primera entrada, veremos los conceptos básicos que nos permitirán entender que es NoSQL y en qué consiste el ataque de inyección para esta tecnología. Vayamos en orden:

¿Qué es NoSQL?

NoSQL (No solo SQL) es un enfoque de bases de datos que no sigue el modelo relacional tradicional basado en tablas. En lugar de utilizar estructuras rígidas con filas y columnas, las bases de datos NoSQL están diseñadas para manejar grandes volúmenes de datos de forma flexible y escalable.

Existen diferentes tipos de bases de datos NoSQL:

  • Clave-valor: Almacenan datos como pares clave-valor, como Redis o DynamoDB.
  • Documentales: Guardan información en documentos JSON o BSON, como MongoDB o CouchDB.
  • Grafos: Optimizadas para gestionar relaciones complejas entre entidades, como Neo4j.
  • Columnares: Organizan datos en columnas en lugar de filas, como Apache Cassandra o HBase.

Este tipo de bases de datos es ideal para aplicaciones que requieren alta disponibilidad, escalabilidad horizontal y procesamiento eficiente de datos no estructurados o semiestructurados.

Algunas BD No-SQL conocidas

La estructura de una base de datos NoSQL depende del tipo específico, pero en general se diferencia de las bases de datos relacionales al no usar tablas con filas y columnas.

Clave-valor: Similar a un diccionario, almacena datos en pares de clave y valor. Ejemplo en Redis:

{
  "usuario123": "Alejandro",
  "puntos": 1500
}

Documental: Almacena datos en documentos JSON o BSON, organizados de forma jerárquica. Ejemplo en MongoDB:

{
  "nombre": "Alejandro",
  "edad": 29,
  "habilidades": ["Seguridad informática", "DevOps", "Cloud"]
}

Grafos: Se basa en nodos y relaciones entre ellos, ideal para modelar conexiones. Ejemplo en Neo4j:

(Alejandro)-[:CONOCE]->(Maria)
(Alejandro)-[:USA]->(BurpSuite)

Columnares: En lugar de filas, almacena datos en columnas para mejorar rendimiento en consultas masivas. Ejemplo en Cassandra:

usuario_id | nombre    | puntos
-----------+----------+-------
123        | Alejandro | 1500

Las ventajas que ofrecen las BD NoSQL han hecho que muchas aplicaciones modernas las vengan utilizando desde hace varios años, ofreciendo mayor flexibilidad y escalabilidad que las BD tradicionales.

¿Es posible inyectar este tipo de bases de datos?

La respuesta en corto es: sí. Sin embargo, las formas tradicionales que son útiles en bases de datos relacionales (ejem. ‘ or 1=1– ) no son iguales en bases No-SQL, por lo que debemos utilizar procedimientos diferentes.

Una inyección NoSQL es un ataque que explota vulnerabilidades en la manera en que las consultas son formuladas hacia este tipo de bases de datos. La manipulación de consultas inseguras dentro de una aplicación, pueden permitir a un atacante evadir controles de autenticación o robar datos.

Sin embargo, como ya mencionamos antes, los métodos tradicionales basados en las palabras o caracteres claves de la sintaxis de una consulta SQL, no nos servirá acá, por lo que es importante entender como trabaja una BD NoSQL.

¿Cómo trabaja una BD NoSQL?

En este punto nos topamos con una primera dificultad. Cada BD NoSQL podría tener una forma diferente de elaborar las consultas, con una sintaxis propia, palabras claves/reservadas diferentes y operadores particulares.

Lo que debemos tener en cuenta es que hay similitudes entre varias de ellas, por ejemplo:

  • Tienen una estructura, la cual generalmente está basada en un identificador y un contenido
  • Utilizan operadores cuando se realizan consultas. Los operadores lógicos generalmente siempre están implementados, ya sea en su forma de texto (OR), o en su forma simbólica (||).
  • Existen parámetros que suelen ser comunes, sobre todo cuando se trata de parámetros de comparación, por ejemplo:
    • $lt =Less Than (Es menor que)
    • $lte = Less Than or Equal (Es menor o igual que)
    • $eq = Equal (Es igual a)
    • $ne = Not Equal (No es igual a)
    • $gte = Greater Than or Equal (Es mayor o igual que)
    • $gt = Greater Than (Es mayor que)
  • Usan palabras clave dentro de la sintaxis de las consultas. Estas se encuentran en la documentación de cada BD, por lo que es primordial primero identificar que BD usa la aplicación que vamos a evaluar, para luego revisar la documentación. Aquí te dejamos algunos enlaces con la documentación de las BD NoSQL más usadas:

Podríamos realmente elaborar un libro completo si nos ponemos a analizar cada BD NoSQL, por lo que teniendo en cuenta los conceptos previos, vamos a realizar los escenarios de inyección en una de las bases más ampliamente usadas: MongoDB

NoSQL Injection en MongoDB

MongoDB no tiene una sintaxis muy compleja para su operación. Al ser una BD NoSQL de tipo documental, se debe construir un «predicado de consulta» que indique los documentos que se desea devolver. Ya que nuestra intención es realizar el ataque de inyección desde una aplicación, es importante haber identificado también el lenguaje de desarrollo de la aplicación, ya que esto nos ayudará a mucho a la hora de armar nuestra query modificada.

Algunas consultas básicas en MongoDB son las siguientes:

  • Seleccionar todos los documentos:
    • db.usuarios.find({}) Esto devuelve todos los documentos de la colección usuarios.
  • Filtrar documentos por criterio
    • db.usuarios.find({ edad: { $gt: 25 } }) Encuentra usuarios con edad mayor a 25.
  • Seleccionar solo ciertos campos
    • db.usuarios.find({ edad: 29 }, { nombre: 1, _id: 0 }) Devuelve solo el campo nombre de los usuarios con edad 29, excluyendo _id.
  • Ordenar resultados
    • db.usuarios.find().sort({ edad: -1 }) Ordena por edad en orden descendente.
  • Contar documentos
    • db.usuarios.countDocuments({ edad: 29 }) Cuenta cuántos usuarios tienen 29 años.
  • Consultar documentos contenidos en varias opciones:
    • db.inventory.find( { status: { $in: [ «A», «D» ] } } ) Recibe todos los documentos en donde el status sea igual a «A» o «D»

Si quieres ver las consultas para los diferentes lenguajes de programación que son compatibles, puedes ir a la documentación: https://www.mongodb.com/docs/manual/tutorial/query-documents/

Ahora que ya tenemos un poco más de contexto, podemos pasar al lado de la seguridad ofensiva. Veamos un ejemplo sencillo de como podemos realizar un ataque de inyección NoSQL:

  • Imaginemos que tenemos una aplicación, y para la autenticación de un usuario tenemos que el username y el password contra la base:
db.users.find({ username: 'admin', password: '123456' })
  • Desde el lado de la aplicación, es el usuario quien debe ingresar esos valores, los cuales serán comparados. Supongamos que el campo password se inyecta, para que luego la consulta sea la siguiente:
{"username":"admin","password":{"$ne": ""}}
  • Si leemos la consulta, está comparando el usuario admin, y la contraseña es cualquier valor que no sea igual a vacío, por lo tanto la consulta es válida y traerá el primer usuario de la base de datos, consiguiendo evadir la autenticación. Si la petición fuera vía GET, la estructura sería la siguiente:
username=admin&password[$ne]=

Si bien este es un ejemplo muy básico, ya que una aplicación bien implementada no basará su autenticación en esta forma simple de asignación de parámetros, el principio es el mismo: necesitamos conseguir inyectar una potencial consulta dentro de la estructura que soporta MongoDB, de tal manera que consigamos realizar alguna acción no contemplada por el desarrollador. Por ejemplo, la siguiente consulta inyectada, podría indicarnos si la contraseña del usuario comienza con la letra ‘a’, basándonos en las respuestas de las peticiones:

{"username":"admin","password":{"$regex":"^a*"}}

La consulta compara el usuario admin, y la contraseña se valida en base a una expresión regular que devuelve un valor si comienza con la letra a. Si la respuesta es diferente a la que se recibe cuando la contraseña es errada, entonces habremos confirmado que la contraseña empieza por ‘a’. Podemos iterar todas las letras hasta obtener la contraseña real.

Herramientas para realizar NoSQL Injection

La mejor opción para un ataque efectivo es realizar las peticiones de forma manual, ya que la aplicación podría tener algún control que bloquee nuestras peticiones en caso de detectar una secuencia automatizada de peticiones. Sin embargo, si hemos hecho nuestra tarea de enumeración bien, y sabemos que no existen tales controles, podemos usar las siguientes herramientas:

Tipos de ataque de inyección NoSQL

Según la documentación de PortSwigger, tenemos dos principales tipos de inyección NoSQL:

  • Inyección de sintaxis – Esta ocurre cuando se intenta quebrar la estructura de la sintaxis de una consulta NoSQL, permitiendote inyectar tu propio payload. Por ejemplo, en este caso se inyectará una consulta para validar si el password de un usuario contiene dígitos:
Query normal: {"$where":"this.username == 'admin'"}
Query inyectada: {"$where":"this.username == 'admin' && this.password.match(/\d/) || 'a'=='b'"}
  • Inyección de operadores – Esta ocurre cuando usas operadores NoSQL para manipular las consultas. Un ejemplo es la inyección de login que vimos antes.

Asimismo, debemos saber que al igual que en una inyección de BD relacional, tenemos diferentes variantes del ataque de inección, como por ejemplo, inyecciones basadas en tiempo (Timing Based Injection), inyecciones basadas en errores (Error based injection) o inyecciones a ciegas (Blind Injection).


Preparando nuestros entornos para la práctica

Ahora ya tenemos la base teórica. Es hora de poner manos a la obra, para ello utilizaremos los siguientes recursos públicos que están disponibles para practicar:

Para instalar Vulnerable Node App, ejecutaremos lo siguiente:

git clone https://github.com/Charlie-belmer/vulnerable-node-app
docker-compose build
docker-compose up

Y se iniciará en el puerto 4000

Para instalar NoSQL Injection App (NIVA), ejecutaremos lo siguiente:

docker pull aabashkin/niva
docker run -p 8080:8080 aabashkin/niva

El servicio se ejecutará en el puerto 8080:

Para el caso del acceso a Portswigger, solo hace falta crearnos una cuenta y entrar a los laboratorios:

Algunos recursos adicionales que pueden ayudarte a tener más conocimiento sobre el tema:

Por ahora dejaremos esta entrada aquí. En la siguiente veremos como resolver los retos de los laboratorios, y poner en práctica lo aprendido.

Esperamos que te haya sido de utilidad.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *