Welcome to another Hack the Box walkthrough. In this blog post, I have demonstrated how I pwned the Soulmate machine on Hack the Box. Hack The Box is a cybersecurity platform that helps you bridge knowledge gaps and prepares you for cyber security jobs.
You can also test and grow your penetration testing skills, from gathering information to reporting. If you are new to this blog, please do not forget to like, comment and subscribe to my YouTube channel and follow me on LinkedIn for more updates.About the Machine
Soulmate is an “easy” Linux machine on Hack the Box that demonstrates a realistic, chained compromise: network and web enumeration, virtual-host discovery, product fingerprinting, exploitation of a known application vulnerability - the CrushFTP authentication bypass (CVE-2025-31161 / CVE-2025-2825), with administrative abuse, insecure file upload, exposed plaintext credentials, and a locally bound Erlang interactive shell to achieve full system compromise, and finally leveraging the interactive Erlang shell to execute commands as root. The box is an excellent exercise in methodical enumeration, product fingerprinting, using responsible POCs in a CTF context, and exploiting local-only services for privilege escalation.
The first step in pwning the Soulmate machine like I have always done in my previous writeups is to connect my Kali Linux terminal with Hack the Box server. To establish this connection, I ran the following command in the terminal:
Once the connection between my Kali Linux terminal and Hack the Box server has been established, I started the Soulmate machine and I was assigned an IP address (10.10.11.86)
After been assigned 10.10.11.86, I decided to map the target machine IP address to the domain name. This way, I could access the service by name instead of by IP address by running:
I added 10.10.11.86 soulmate.htb to the /etc/hosts file and performed reconnaissance using Nmap to find all the open port and services associated with the target machine. Using the following command, I found all the services and port running at 10.10.11.86:
This scan performs service detection, grabs version information, runs default NSE scripts, and attempts OS detection - all of which help build an early understanding of the target environment.
Nmap revealed two open TCP ports:
- Port 22 (SSH) running OpenSSH 8.9p1 on Ubuntu
- Port 80 (HTTP) served by nginx 1.18.0
The SSH service doesn’t appear to have any unusual configurations, but it might later be useful for gaining shell access once valid credentials are obtained.
The web server on port 80 looks more promising. The HTTP response headers show it’s powered by nginx/1.18.0 (Ubuntu)
, and the site title reads “Soulmate - Find Your Perfect Match.” This suggests it’s likely a custom web application - possibly some kind of dating or matching platform.
Interestingly, the Nmap script output noted that the httponly
flag was not set on the PHPSESSID
cookie. This could make the site vulnerable to client-side script access or session hijacking, so it’s something worth noting for later during web exploitation.
Directory enumeration (dirsearch)
I ran a quick directory brute-force against the site with dirsearch
to see what hidden endpoints and pages the web app exposes:
What dirsearch found
The scanner discovered a handful of useful endpoints and a clear authentication flow. First, the /assets
path exists but returns 403 Forbidden for directory listing - the server prevents browsing the static asset directory, although a plain /assets
request was redirected to /assets/
. That tells us static files (JS/CSS/images) are likely served from there but directory indexing is off.
Several hits immediately reveal that many user-facing pages are protected behind an authentication workflow:
- A request for
/dashboard.php
responds with a 302 redirect to/login
, which means the dashboard requires authentication and the app redirects unauthenticated users to the login page. /profile.php
behaves the same — it redirects to/login
, so profile pages are also protected./logout.php
redirects tologin.php
, confirming the site implements a login/logout flow.
At the same time, dirsearch shows two publicly accessible pages:
/login.php
returns 200 and served up the login page (about 8 KB). This is our entry point to try authentication and to inspect the login form for hidden fields, CSRF tokens, or client-side logic./register.php
also returned 200 (about 11 KB). The presence of a registration page is important — it means we can create a user account, which is often the cleanest way to get an authenticated user for further testing.
Virtual-host (vhost) discovery - ffuf
To enumerate possible virtual hosts on the target I ran a host-header fuzz with ffuf
, replacing the Host:
header with FUZZ.soulmate.htb
so the webserver would reveal any sites it serves by hostname:
This technique forces the target to answer as if the browser requested something.soulmate.htb
. Many web servers (and reverse proxies) route requests based on the Host
header, so a response that differs from the default host often indicates a hidden or secondary site.
ffuf ran against 167378 names and returned a single interesting result:
ftp.soulmate.htb
— HTTP 302 redirect (empty body)
Next, I added ftp.soulmate.htb
to /etc/hosts
pointing at the target IP, then browse http://ftp.soulmate.htb/
to observe the redirect target and full behavior:
I added the hostname ftp.soulmate.htb to /etc/hosts
and began targeted probing of the vhost to uncover any hidden functionality or misconfigurations.
Exploiting the FTP virtual-host — CrushFTP auth bypass (CVE-2025-31161 / CVE-2025-2825)
When I browsed to the ftp.soulmate.htb
vhost it immediately redirected me to a web login:
(http://ftp.soulmate.htb/WebInterface/login.html)
The page looked like a standard CrushFTP web interface, the logo and layout gave it away and only exposed a typical username/email and password login form without a registration button.
Since there was no way to self-register a privileged account via the UI, I started hunting for product-specific vulnerabilities. A quick search for “CrushFTP exploit” turned up a proof-of-concept for an authentication bypass (CVE-2025-31161 / CVE-2025-2825) that can create a new user with admin privileges if the server is vulnerable.
I cloned the POC repo and ran the exploit to create an admin user:
Then I moved into the directory "CVE-2025-31161". To execute the exploit, I ran:
The exploit output showed the expected workflow and success messages:
- It warmed up and confirmed the target was reachable.
- It sent the “create account” payload.
- The script reported:
User created successfully
. - Final message:
Exploit Complete you can now login with Username: boltech Password: boltech
.
The exploit returned “User created successfully”, and those credentials allowed me to log into the CrushFTP web interface as an administrator, opening a path to recover configuration files, secrets, and further post-exploitation options.
Verification & immediate impact
After the exploit completes, the next step is to verify the credentials by logging into the web interface:
- I opened
http://ftp.soulmate.htb/WebInterface/login.html
. - Entered both the
Username: boltech and
Password: boltech
. - The login was successful and I landed in the CrushFTP admin/dashboard UI.
A successful login grants administrative access to the CrushFTP interface, which typically allows:
- Listing and downloading user configuration files (often containing plaintext passwords or keys).
- Access to server settings, virtual paths, and possibly file listings for hosted shares.
- Uploading or adding job/task definitions (which can be abused for code execution or persistence).
- Viewing logs and other sensitive information.
Admin takeover and user pivot — resetting a user password via the CrushFTP dashboard
After creating the admin account and logging into the CrushFTP web UI as boltech
, I immediately explored the administrative interface to see what I could control.
I clicked Admin (top-left) to open the server admin dashboard. The dashboard shows standard server telemetry - Server Status, Job Schedules, Session Information, Login Information, Bytes Transferred, Threads, Memory, etc. This confirms I have full administrative visibility into the service.
Next I opened User Manager (highlighted in the UI). The user manager lists all accounts configured on the CrushFTP instance.
I found a user named ben in the list and opened that user’s profile. The user profile page exposes account management functions, including a field to set/change the user’s password.
Because I had admin privileges, I changed ben’s password to boltech, saved the modification, then logged out of the boltech
admin account.
Remember to save the changes and you will receive a toast notification "User settings saved". Make sure to logout and sign in with the new credentials. To logout of user boltech, click on "Files" on the top left corner of the page.
Then click on the user icon "Boltech" and click "Logout". This will sign you out and redirect you to the login page.
To verify the change and prove the account takeover, I navigated back to the normal login page and signed in with:
- Username:
ben
- Password:
boltech
The login succeeded - I was authenticated as ben, demonstrating that an admin-created/modified account can be used to pivot to other user contexts without needing the original credentials.
If the credentials works, you will receive a toast notification saying "Success loading webinterface..."
Upload → webshell → user shell — stepping from admin UI to a foothold
After taking over the CrushFTP instance and resetting ben
’s password, I switched contexts and logged in as ben
to inspect that user’s file space.
After logging in as ben
I found a webProd
directory with an Add files functionality and used it to upload a PHP reverse shell (revshell.php)
- a perfect place to drop a webshell.
Before uploading any file, I visited Pentest Monkey GitHub Repository and downloaded the PentestMonkey PHP reverse shell and edited it to point back at my Kali box (10.10.15.42:4444
), saving it as revshell.php
.
Then I started a netcat listener on my machine:
I uploaded revshell.php
via the CrushFTP web interface (webProd → Add files
) and waited to get a rebound shell on the netcat listener.
After uploading the revshell.php file, it appeared on the list of files in the webProd directory and it can be seen in the screenshot below.
To trigger the payload, I open http://soulmate.htb/revshell.php
. in the browser and it was successful.
Result - a shell callback
My netcat listener immediately received a connection from the target (10.10.11.86):
Quick reconnaissance from the shell:
- Listing the root filesystem (
ls
) confirmed normal Linux layout and presence oflab.txt
in/
. - Checked
/tmp
and saw a number of files includinglinpeas.sh
andCVE-2025-32433.py
— common CTF artefacts.
Then I inspected /usr/local/lib/erlang_login/start.escript
and found the following (relevant excerpt):
Why this is important:
That script does two useful things for us:
- It spawns an SSH daemon on port 2222, but it binds to 127.0.0.1, meaning the SSH service listens only on the loopback interface (not directly reachable remotely).
- It contains plaintext credentials for
ben
(HouseH0ldings998
) in{user_passwords,...}
, so we now possess a legitimate user password.
Privilege pivot — SSHing in as ben
and capturing user.txt
With the plaintext credentials recovered from the target (ben:HouseH0ldings998
), I authenticated over SSH to the box to get a stable user shell:
Listing ben
’s home directory showed a single file, user.txt
. I read it to capture the user flag:
Service discovery from ss -tuln
I ran ss -tuln
from the ben
shell to get a quick view of network services on the box:
ss -tuln
revealed public HTTP (80) and SSH (22), plus several loopback-only services (2222, 8080, 8443, 9090, 4369, etc.). These local-only services are high priority because they often house admin interfaces or internal APIs. From our webshell / local SSH we can query these directly (as we did with the 2222 SSH daemon) or tunnel them back to our machine to escalate further.
Why loopback services matter for exploitation
Services bound to 127.0.0.1
are not reachable from the attacker's machine directly, but they often hold high-value interfaces (admin panels, internal APIs, debug consoles, inter-process management) and sometimes accept weaker authentication because they’re assumed to be local-only. If you can execute code on the box (webshell / user shell), you can talk to these services directly — which is exactly what we did by SSHing locally to port 2222. Other loopback services may similarly expose secrets or escalation vectors.
After enumerating open ports with ss -tuln
, I noticed something interesting running locally on port 2222, which wasn’t exposed externally. To check what was listening, I used netcat to connect directly to it:
Surprisingly, the service immediately responded with an SSH banner — but not a typical one. Instead of OpenSSH
, it identified itself as SSH-2.0-Erlang/5.2.9
. This hinted that the service might be using the Erlang runtime to implement some kind of custom SSH server — definitely something worth digging into.
Since it behaved like SSH, I decided to actually connect to it using the SSH client:
After accepting the host key, I was prompted for a password. Using the same credentials as my current user (ben
), I successfully authenticated - but what I got wasn’t a standard shell. Instead, I landed inside the Erlang interactive shell (also known as the Eshell
):
At first glance, it wasn’t clear what I could do, but typing help().
revealed a huge list of internal commands and functions. Erlang is designed for distributed, concurrent systems, and its shell allows direct interaction with the underlying system if not properly sandboxed - a major security flaw if accessible by unprivileged users.
Scrolling through the command list, one function immediately caught my attention:
This allows executing system commands directly from the Erlang shell. That’s essentially remote code execution with system-level access.
To confirm this, I tried running a basic command:
The output was shocking:
That meant I had root access through this Erlang shell! From there, it was game over. I could execute any command as the system’s root user. To wrap things up, I went ahead and grabbed the final flag:
And the flag appeared:
Root compromise achieved. Hurray!!! I got the root flag. With that, the machine was officially pwned
If you enjoy reading my walkthrough, do not forget to like, comment, and subscribe to my YouTube channel and also connect with me on LinkedIn. Also, don't forget to turn on post notification on my YouTube channel and Medium to get notification as soon as I write.
Subscribe to my YouTube channel and Follow me on: LinkedIn | Medium | Twitter | Boltech Twitter | Buy Me a Coffee
Keywords:
Soulmate Hack the Box Writeup
Soulmate Hack the Box Machine Writeup
Soulmate Hack the Box Walkthrough
Soulmate Hack the Box Machine Walkthrough
Soulmate HTB Writeup
Soulmate HTB Walkthrough
Soulmate HTB Machine Walkthrough
Soulmate HTB Machine Writeup
Soulmate Hack the Box HTB Machine Walkthrough Writeup
Soulmate Hack the Box Solution
Soulmate Machine Walkthrough
HTB Soulmate
Soulmate Machine Write-up
HackTheBox Soulmate Writeup
soulmate htb writeup
soulmate htb walkthrough
soulmate hack the box writeup
soulmate hack the box writeups
soulmate htb walkthroughs
soulmate htb solution
soulmate htb writeups
soulmate htb complete writeup
soulmate htb complete walkthrough
0 Comments