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.
Observamos que podemos modificar el parámetro “category”.
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.
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 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 |
Si actualizamos la página vemos que el laboratorio se resolvió correctamente
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.
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.
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.
Sin embargo, cuando tratamos de acceder con el usuario administrator, no es posible.
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.
Accedemos correctamente a la cuenta de administrator.
Vamos al panel principal y el laboratorio estará resuelto
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.
Probamos cambiar el request method para un mejor control de los parámetros.
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) |
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 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.
Aplicamos el payload con el usuario Administrator para saber el valor de la longitud y encontramos que es de 8 caracteres
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.
Vemos que los caracteres correctos son aquellos response con longitud de 209.
La contraseña de administrator es ubtdtlfy. Probamos la credencial y resolvemos el laboratorio.
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.
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.
Realizamos la iteración sobre where para extraer los nombres de los campos.
Payload: “Object.keys(this)[0].match(‘^.{pos}char.*’)”
Descubrimos que el primer campo es id.
Podemos cambiar el índice del payload y descubrimos que el segundo campo es username.
El tercer campo es password
El 4to campo es email
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
Ahora volvemos a fuzzear el 5to campo e identificamos que se generó uno llamado changePwd (Atentos con la mayúscula).
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.*')"
Observamos que el valor de changePwd es b20ec7168c6c8381
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.
Ingresamos el valor encontrado previamente: b20ec7168c6c8381 y nos carga una ventana para generar una nueva clave
Le ponemos la clave que querramos y accedemos nuevamente al usuario Carlos con la clave generada.
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.