Write-Up Portswigger NoSQL Injection

Autor: https://github.com/hugorivas2101

LABORATORIOS PORTSWIGGER - NoSQL Injection

Comparto el solucionario que del módulo de NoSQL Injection de Portswigger. Los apuntes de teoría hechos en cada laboratorio es un resumen de lo enseñado por Portswigger. Espero que les guste :D.

LAB 1 : Detecting NoSQL injection

💡 Enunciado

The product category filter for this lab is powered by a MongoDB NoSQL database. It is vulnerable to NoSQL injection. To solve the lab, perform a NoSQL injection attack that causes the application to display unreleased products.

Accedemos al laboratorio y seleccionamos cualquier categoría. Esta solicitud la interceptamos con Burpsuite.

image.png

Observamos que podemos modificar el parámetro “category”.

image.png

Escribimos un caracter inválido en el parámetro y nos da un mensaje de error en el sistema. En este mensaje podemos ver que la base de datos usada es MongoDB.

image.png

Podemos realizar una inyección basada en sintaxis. Trataremos de inyectar condicionales booleanas usando sintaxis NoSQL. Para esto usamos las condicionales

Escribir un payload para explotar la vulnerabilidad NoSQL Injection. Insertamos una condiciona verdadera y otra falsa en el parámetro “Category”

somexd' && 1 && 'x
somexd' && 0 && 'x

Hay que recordar que estos valores deben de estar URL encodeados cuando realizamos la petición por burpsuite. Observamos que la aplicación se comporta de forma diferente, por lo que la condición falsa impacta en la lógica de la solicitud.

Condicional true en el parámetro Category: category=Gifts'+%26%26+1+%26%26+'x

Condicional true en el parámetro Category: category=Gifts’+%26%26+1+%26%26+’x

Condicional false en el parámetro Category: category=Gifts'+%26%26+0+%26%26+'x

Condicional false en el parámetro Category: category=Gifts’+%26%26+0+%26%26+’x

Una vez identificado que podemos modificar condiciones booleanas, podemos intentar sobreescribir las condiciones actuales para explotar la vulnerabilidad. Podemos inyectar una condición JS que sea siempre verdadera, como

somexd'||1||'
'||'1'=='1

Además, podríamos agregar un caracter nulo para que se ignoren las condiciones adicionales en la query. Por ejemplo, si asumimos que la query es la siguiente, donde released nos dice si el producto ya está publicado:

this.category == 'fizzy' && this.released == 1

El atacante podría construir un payload de tal forma que se ignore lo siguiente, agregando un caracter nulo (%00 o \u0000 urlencoded)

this.category == 'fizzy'\u0000' && this.released == 1
![Parámetro Category: category=somexd’   1   ‘%2500](/assets/images/write-up-portswigger-nosql/image%205.png)
Parámetro Category: category=somexd’   1   ‘%2500

Si actualizamos la página vemos que el laboratorio se resolvió correctamente

image.png

LAB 2 : Exploiting NoSQL operator injection to bypass authentication

💡 Enunciado

The login functionality for this lab is powered by a MongoDB NoSQL database. It is vulnerable to NoSQL injection using MongoDB operators. To solve the lab, log into the application as the administrator user. You can log in to your own account using the following credentials: wiener:peter.

Accedemos al panel de login para acceder con las credenciales que nos dan en la descripción del laboratorio.

image.png

Interceptamos esta petición y vemos que si las credenciales son correctas, nos sale un código de estado 302 para redirigirnos a la página de inicio. En otro caso, el response tiene un código de estado 200 en el que se menciona que tenemos credenciales inválidas.

image.png

Un poco de teoría…

Las bases de datos NoSQL usan operadores los cuales son formas de especificar condiciones que la data debe cumplir para ser incluida en el resultado de la query, por ejemplo

  • $ne: Hace referencia a not equal, lo que significa que busca coincidencias con todos los valores que no son iguales a un valor específico
  • $regex: Selecciona los documentos donde los valores coinciden con el regex especificado.

Podemos insertar estas querys en JSON o directamente en la URL

  • En json: Normalmente la petición sería {”username”:”nametest”}, pero podríamos cambiarlo por {”username”:{”$ne”:”invalid”}}.
  • Para URL: Normalmente la petición sería username=nametest, pero podríamos cambiarlo por username[$ne]=invalid. Si no se puede directamente en la URL, podemos cambiar el request method por un POST, cambiar el header Content-Type a application/json y realizar la inyección desde json.

Podemos testear cada input inyectando la query {”$ne”:”invalid”}. En caso inyectemos este payload en el usuario y la contraseña y sea efectivo, accederemos a la aplicación como el primer usuario de la colección.

Si queremos seleccionar una cuenta en específico, podríamos construir el payload para que incluya nombres conocidos

{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}

Volvemos al laboratorio

El objetivo del laboratorio es acceder con el usuario administrator, por lo que realizamos la técnicas aprendidas. Comprobamos que el login es vulnerable a NoSQL injection usando la condición $ne en password.

image.png

Sin embargo, cuando tratamos de acceder con el usuario administrator, no es posible.

image.png

Probablemente esto se deba a que no existe un usuario con ese nombre, por lo que probamos con las expresiones regulares. Observamos que el usuario no tiene nombre de exactamente administrator, sino de admin3xlvdls4.

image.png

Accedemos correctamente a la cuenta de administrator.

image.png

Vamos al panel principal y el laboratorio estará resuelto

image.png

LAB 3 : Exploiting NoSQL injection to extract data

💡 Enunciado

The user lookup functionality for this lab is powered by a MongoDB NoSQL database. It is vulnerable to NoSQL injection. To solve the lab, extract the password for the administrator user, then log in to their account. You can log in to your own account using the following credentials: wiener:peter.

Accedemos al panel de login e interceptamos las peticiones que se generan luego de que te autentiques con el usuario wiener. Nos centramos en el endpoint /user/lookup.

image.png

Probamos cambiar el request method para un mejor control de los parámetros.

image.png

Un poco de teoría…

En algunas bases de datos NoSQL, algunos operadores pueden ejecutar código js, como el operador $where en mongo y la función mapReduce(). Por lo que si una aplicación vulnerable usa estos operadores o funciones, la base de datos podría evaluar el js como parte de la query y habilita la posibilidad de extraer data de la base de datos.

Pongamos un ejemplo: Vemos que la url tiene como argumento username.

https://insecure-website.com/user/lookup?username=admin

Esto produce la siguiente query

{"$where":"this.username == 'admin'"}

Dado que la query usa el operador $where, podemos inyectar funciones de js para que retorne datos. Pongamos el payload:

# Para que retorne el primer caracter de la password
admin' && this.password[0] == 'a' || 'a'=='b

# Para identificar si la password tiene digitos
this.password.match(/\d/) || 'a'=='b

Además, podemos identificar campos de la bbdd usando el siguiente payload

https://insecure-website.com/user/lookup?username=admin'+%26%26+this.password!%3d'

Debemos comprobar que el campo exista. Dado que sabemos que el campo “username” existe, podemos enviar el siguiente payload para comparar las respuestas

admin' && this.username!=' 
admin' && this.foo!='

Si el response es igual que el primero, entonces el campo password existe. Caso contrario, no existe.

Volvemos al laboratorio

Identificamos el endpoint /user/lookup. Detectamos que el parámetro user es vulnerable a la inyección de condicionales. Notamos que luego de inyectar la condicional nos da los datos de administrator, que probablemente sea el primer usuario de la colección.

![Payload usado: wiener’   ‘1’==’1 (URL ENCODED)](/assets/images/write-up-portswigger-nosql/image%2016.png)
Payload usado: wiener’   ‘1’==’1 (URL ENCODED)

Probamos a comprobar cada caracter de la contraseña. Sabemos que la contraseña de wiener es peter, por lo que procedemos a comprobar si nuestro payload es efectivo.

Probamos el primer caracter. Es un resultado inválido.

Probamos el primer caracter. Es un resultado inválido.

Probamos el primer caracter. Es un resultado válido.

Probamos el primer caracter. Es un resultado válido.

Asimismo, podemos saber la longitud de la contraseña. Para esto ir acotando la longitud,

Payload utilizado: `wiener' && this.password.length < 30 || 'a' == 'b`
Este payload permite evaluar si la longitud de la contraseña del usuario "wiener" es menor a 30 caracteres. Se añade la condición falsa `|| 'a' == 'b'` para cerrar correctamente la expresión y evitar errores de sintaxis, ya que la entrada del usuario finaliza con una comilla simple. Así, si la condición de longitud se cumple, la expresión completa será verdadera, lo que permite inferir la longitud real de la contraseña.

Payload utilizado: wiener' && this.password.length < 30 || 'a' == 'b Este payload permite evaluar si la longitud de la contraseña del usuario “wiener” es menor a 30 caracteres. Se añade la condición falsa || 'a' == 'b' para cerrar correctamente la expresión y evitar errores de sintaxis, ya que la entrada del usuario finaliza con una comilla simple. Así, si la condición de longitud se cumple, la expresión completa será verdadera, lo que permite inferir la longitud real de la contraseña.

Aplicamos el payload con el usuario Administrator para saber el valor de la longitud y encontramos que es de 8 caracteres

image.png

Ahora podemos realizar una fuerza bruta para saber la contraseña. Utilizamos solo las 26 letras del abecedario en inglés. y números de 0 al 7 para iterar sobre cada índice de la contraseña.

image.png

Vemos que los caracteres correctos son aquellos response con longitud de 209.

image.png

La contraseña de administrator es ubtdtlfy. Probamos la credencial y resolvemos el laboratorio.

image.png

LAB 4 : Exploiting NoSQL operator injection to extract unknown fields

💡 Enunciado

The user lookup functionality for this lab is powered by a MongoDB NoSQL database. It is vulnerable to NoSQL injection. To solve the lab, log in as carlos.

Un poco de teoría…

Incluso cuando la query original de conulta no utiliza operadores para ejecutar js, se puede inyectar un operador.

Para detectar si se pueden inyectar operadores, podemos agregar el operador $where como un parámetro adicional, en el que evaluamos una condición falsa y otra verdadera.

{"username":"wiener","password":"peter", "$where":"0"}
{"username":"wiener","password":"peter", "$where":"1"}

Si hay una diferencia en los responses, esto podría indicar que la expresión en $where está siendo evaluada.

Si podemos inyectar el operador que habilite la ejecución de js, podríamos usar el método keys para extraer los nombres de los campos. El siguiente payload inspecciona el primer campo del objeto usuario y retorna el primer caracter del campo. Esto es útil para extraer el nombre del campo caracter por caracter.

"$where":"Object.keys(this)[0].match('^.{0}a.*')"

También podemos extraer la data usando los operadores de regex.

{"username":"myuser","password":"mypass"}
{"username":"admin","password":{"$regex":"^.*"}}

Si los response son diferentes a cuando envías una password incorrecta, entonces la aplicación podría ser vulnerable. Para extraer el dato caracter por caracter podemos usar el siguiente payload

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

Volvemos al laboratorio

El enunciado del laboratorio nos menciona que debemos acceder como Carlos, pero no tenemos las credenciales respectivas. Logramos bypassear el login utilizando la técnica del operador $ne . Sin embargo, el mensaje muestra que la cuenta está bloqueada.

image.png

Podríamos intentar inyectar otros pármetros (Como el operador $where) para ver si podemos ejecutar js. Notaremos que aparecen mensajes diferentes con distintos valores de where.

image.png

image.png

Realizamos la iteración sobre where para extraer los nombres de los campos.

Payload: "Object.keys(this)[0].match('^.{pos}char.*')"

Payload: “Object.keys(this)[0].match(‘^.{pos}char.*’)”

Descubrimos que el primer campo es id.

image.png

Podemos cambiar el índice del payload y descubrimos que el segundo campo es username.

image.png

El tercer campo es password

image.png

El 4to campo es email

image.png

Cuando fuzzeamos por el 5to campo, ya nos sale un error. Debido a que el enunciado menciona que hay un campo oculto, identificamos que podemos generar este campo luego de que se envíe la solicitud para cambiar de contraseña al usuario

image.png

Ahora volvemos a fuzzear el 5to campo e identificamos que se generó uno llamado changePwd (Atentos con la mayúscula).

image.png

Con este campo identificado, podemos extraer la información que almacena. Para esto usamos el siguiente payload, que se encarga de leer el contenido dentro de la 4ta clave. Vamos a iterar a través de pos y char.

"$where":"this[Object.keys(this)[4]].match('^.{pos}char.*')"

image.png

Observamos que el valor de changePwd es b20ec7168c6c8381

image.png

Para lograr el reinicio de la contraseña, debemos capturar la solicitud GET que se hace al endpoint forgot-password y agregar el parámetro que descubrimos previamente: changePwd. Observamos que al poner un token inválido, nos retorna un mensaje de error.

image.png

Ingresamos el valor encontrado previamente: b20ec7168c6c8381 y nos carga una ventana para generar una nueva clave

image.png

Le ponemos la clave que querramos y accedemos nuevamente al usuario Carlos con la clave generada.

image.png

Conclusiones

El material ofrecido por PortSwigger es bastante completo. Tiene lo suficiente para resolver y entender completamente todos los laboratorios, los cuales estuvieron muy divertidos de resolver.

Gracias por su lectura :D.