HTB – Silentium

In this walkthrough, I detailed how I achieved full compromise of Silentium on HackTheBox

by Croclius | Apr 23, 2026 | 0 comments

https://www.hackthebox.com/machines/Silentium


Reconnaissance

nmap/TCP

Nmap found the port 22 and port 80 to be open.

croc@hacker$ rustscan -a 10.129.37.152 --ulimit 5000 -- -A -Pn                    
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Real hackers hack time ⌛

[~] The config file is expected to be at "/home/croc/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.129.37.152:22
Open 10.129.37.152:80
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} -{{ipversion}} {{ip}} -A -Pn" on ip 10.129.37.152
Depending on the complexity of the script, results may take some time to appear.
[~] Starting Nmap 7.98 ( https://nmap.org ) at 2026-04-21 03:55 -0400
Nmap scan report for 10.129.37.152
Host is up, received user-set (0.23s latency).
Scanned at 2026-04-21 03:55:55 EDT for 19s

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN9Ju3bTZsFozwXY1B2KIlEY4BA+RcNM57w4C5EjOw1QegUUyCJoO4TVOKfzy/9kd3WrPEj/FYKT2agja9/PM44=
|   256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9qI0OvMyp03dAGXR0UPdxw7hjSwMR773Yb9Sne+7vD
80/tcp open  http    syn-ack ttl 63 nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://silentium.htb/
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.24.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
TCP/IP fingerprint:
OS:SCAN(V=7.98%E=4%D=4/21%OT=22%CT=%CU=36596%PV=Y%DS=2%DC=T%G=N%TM=69E72D9E
OS:%P=x86_64-pc-linux-gnu)SEQ(SP=100%GCD=1%ISR=103%TI=Z%CI=Z%II=I%TS=A)OPS(
OS:O1=M552ST11NW7%O2=M552ST11NW7%O3=M552NNT11NW7%O4=M552ST11NW7%O5=M552ST11
OS:NW7%O6=M552ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN(
OS:R=Y%DF=Y%T=40%W=FAF0%O=M552NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS
OS:%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=
OS:Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=
OS:R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T
OS:=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=
OS:S)

Uptime guess: 3.964 days (since Fri Apr 17 04:47:49 2026)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=256 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   228.02 ms 10.10.14.1
2   228.20 ms 10.129.37.152

Nmap done: 1 IP address (1 host up) scanned in 19.95 seconds
           Raw packets sent: 34 (2.306KB) | Rcvd: 27 (1.858KB)

As the port 80 redirects to the http://silentium.htb/, we need to have an entry in the /etc/hosts file pointing to that:

croc@hacker:~/$ sudo sed -i '$a10.129.37.152\tsilentium.htb' /etc/hosts
Think Box
From here, we'll move on to testing port 80, as it makes more sense to focus on the web application before touching SSH.

HTTP - 80/TCP

Business Logic

Let's start with the Happy Path Testing of the website while keeping burp suite running in the background.

image 1

We have this functionality of a calculator labelled under "Institutional Loan Structuring".

image 2
Note

This calculator is purely client-side JavaScript with all calculations happening in the browser using the amortization formula with no server requests made whatsoever.

You can verify it yourself: Open DevTools → Network tab → move the sliders. You'll see zero new network requests firing. Everything is already loaded on page load.

We also found the following employee names on the site, which could serve as potential usernames.

image 3

I will go ahead and note these in my notepad.

image 5
Note

Moreover, we also see a .js file in the footer of the main page which can be accessed at assets/app.js.

Directory Busting

Directory busting revealed the directory of /assets which gives a 403 upon access.

croc@hacker:~/HTB$ sudo dirsearch -u http://silentium.htb/ -w /usr/share/wordlists/dirb/common.txt 

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 4613

Output File: /home/croc/HTB/reports/http_silentium.htb/__26-04-21_04-17-45.txt

Target: http://silentium.htb/

[04:17:45] Starting: 
[04:17:53] 301 -  178B  - /assets  ->  http://silentium.htb/assets/

Task Completed

image

Subdomain Enumeration

Moving on to subdomain enumeration, we managed to find a staging subdomain.

croc@hacker:~/HTB$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
  -u http://silentium.htb \
  -H "Host: FUZZ.silentium.htb" \
  -fs 178

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://silentium.htb
 :: Wordlist         : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.silentium.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 178
________________________________________________

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

Added the staging.silentium.htb inside of /etc/hosts and opened up in the browser. It shows a login portal for a platform called Flowise, which is meant to be used for building AI agents.

image 6

Username Enumeration

On the login portal, the API returns different responses depending on whether a user exists or not.

image 8

For example, using croc@croclius.com returned a 404 User Not Found, while ben@silentium.htb returned a 401 Incorrect Email or Password.

image 9

This confirms that Ben is a valid user, meaning we can enumerate valid users simply by observing the response codes and messages.

Think Box
This is where the list of usernames that we made earlier comes in handy. We can create a list of possible usernames and brute force to try to find all the valid usernames.

Valid Usernames

Saved all of the following combinations into the emails.txt file:

After that, used ffuf to enumerate all valid usernames. Note that it is filtering the 404 automatically.

croc@hacker:~/HTB/selentium$ ffuf -w emails.txt \
  -u http://staging.silentium.htb/api/v1/auth/login \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"FUZZ","password":"dummy"}'

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : POST
 :: URL              : http://staging.silentium.htb/api/v1/auth/login
 :: Wordlist         : FUZZ: /home/croc/HTB/selentium/emails.txt
 :: Header           : Content-Type: application/json
 :: Data             : {"email":"FUZZ","password":"dummy"}
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

ben@silentium.htb       [Status: 401, Size: 85, Words: 4, Lines: 1, Duration: 245ms]
:: Progress: [5/5] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::

Forgot Password

Next, I explored the forgot password functionality using ben@silentium.htb.

image 10

Out of curiosity, I also clicked on that link above to change my password if I got a reset code:

image 11

Inside of burp, I see this response where we have this tempToken in the response which seems to be the password reset token and we can try using it above to update the password.

image 12
Note

The "credential":"$2a$05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhG" is the bcrypt password hash. It is Ben's actual hashed password stored in the database and the API is leaking it directly in the response. Note that Ben is also the admin of the web app.

Updating the Ben's Password

We can use this token to try to reset the user's password:

image 13

Using the tempToken returned in the response, we were able to reset Ben's password directly without any email verification, and the application returned a success response.

Now, we can log in into the application.

image 14
Think Box
From here, the next logical step in my opinion is finding out known exploits for this version of Flowise. But for that we need to know the exact version of our web application. So, let's find that out.

Shell as

Finding the Version

You can see the version of the web app in the top right corner under the settings icon:

image 15
Note

You can also use the API endpoint of /version inside of Burp.

image 16

Looking for CVEs

I asked Claude to find all the CVEs for this version and it provided me three.

NVD - CVE-2025-59528
faviconnvd.nist.gov

NVD - CVE-2025-8943
faviconnvd.nist.gov

NVD - CVE-2025-26319
faviconnvd.nist.gov
Think Box
Looking at all three, the first one stands out the most in my opinion, because it is well-documented. So, let's begin with that one.

Understanding CVE-2025-59528

The following article from SentinelOne provides a clear breakdown of the attack flow.

FY25 Homepage SocialCard
CVE-2025-59528: Flowiseai Flowise RCE Vulnerability
CVE-2025-59528 is a remote code execution vulnerability in Flowiseai Flowise. Learn about its impact, affected versions, and mitigation methods.
faviconwww.sentinelone.com

In a nutshell, the user input as inputString is passed to the Function() constructor without being properly sanitized in the convertToValidJSONString function.

function convertToValidJSONString(inputString: string) {
    try {
        const jsObject = Function('return ' + inputString)()
        return JSON.stringify(jsObject, null, 2)
    } catch (error) {
        console.error('Error converting to JSON:', error)
        return ''
    }
}

This makes it equivalent to the eval() function. When user-controlled data is passed directly to this constructor without proper validation, it creates a direct path for arbitrary code execution.

Since Flowise runs as a Node.js application, executed code has access to the full Node.js runtime environment, including dangerous built-in modules such as child_process which can be used to execute system commands.

Note

The following is the attack path from here which we will perform step-by-step now:

                        [Attacker]
                            |
                            | Identifies exposed Flowise v3.0.5 instance
                            v
                  [Target Flowise Instance]
                            |
                            | Accesses CustomMCP node configuration interface
                            v
                       [CustomMCP Node]
                            |
                            | Submits crafted mcpServerConfig string
                            | containing malicious JavaScript payload
                            v
                   [Server-Side Processing]
                            |
                            | Passes unsanitized input to Function() constructor
                            v
                   [Function() Constructor]
                            |
                            | Evaluates and executes malicious JavaScript
                            v
                 [Node.js Runtime - Full Privileges]
                            |
           _________________|_________________
          |                 |                 |
          v                 v                 v
    child_process           fs               net
  Execute system      Read / Write     Establish network
    commands           filesystem        connections

Exploiting CVE-2025-59528

image 17

Written by

Croclius

Croclius

Ethical Hacker / Penetration Tester

Join my Newsletter

Stay in the loop.

The latest writeups, research, and offensive security insights — delivered straight to your inbox. No spam, ever.

Please enter your first name.
Please enter a valid email address.

No spam, ever. Unsubscribe anytime.

Comments

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.