
PoC y write-up para la escalada de privilegios de "Nexus" en HackTheBox: servicio de sincronización de plantillas de Gitea ejecutado como root, vulnerable a path traversal mediante objetos Git falsificados. Solo para uso educativo.
Path traversal en la sincronización de plantillas de Gitea mediante objetos Git falsificados → root
Un exploit de prueba de concepto para la escalada de privilegios en la máquina de HackTheBox Nexus.
Un servicio systemd propiedad de root sincroniza los repositorios de plantillas de Gitea en disco
sin sanear los nombres de las entradas, lo que permite a un usuario autenticado con bajos privilegios
escribir archivos arbitrarios como root mediante directory traversal — usado aquí para implantar
una clave SSH en /root/.ssh/authorized_keys.
Este código se proporciona para fines educativos y pruebas de seguridad autorizadas únicamente — es decir, entornos de laboratorio como HackTheBox, o sistemas que poseas o para los que tengas permiso explícito por escrito para probar. El uso no autorizado contra sistemas que no controlas es ilegal. El autor no asume ninguna responsabilidad por el mal uso.
Si la máquina Nexus sigue activa en HackTheBox, no publiques ni compartas este exploit — las reglas de HTB prohíben distribuir soluciones de máquinas activas.
El script de sincronización lee archivos de repositorios marcados como plantillas en Gitea y los escribe en el sistema de archivos local. La ruta de destino se construye aproximadamente así:
dest = os.path.join(base_dir, name) # name comes straight from the Git tree
open(dest, "w").write(content)
os.path.join() no neutraliza las secuencias ... Una entrada de tree cuyo nombre
sea una ruta de traversal (p. ej. ../../../../../root/.ssh/authorized_keys) hace por tanto
que el proceso privilegiado escriba fuera del directorio previsto.
Git almacena el contenido y los nombres por separado:
nombre → objeto (aquí es donde vive el nombre del archivo);El nombre de archivo en una entrada de tree es solo texto, por lo que puede ser .. — pero el cliente
Git estándar se niega a crear tal entrada, precisamente para prevenir este ataque.
Este PoC elude esa comprobación del lado del cliente escribiendo objetos Git en bruto directamente
en .git/objects/ (calculando los ids SHA-1 a mano) y creando refs/heads/main
manualmente. El repositorio enviado contiene entonces una entrada de tree llamada .., que el
proceso de sincronización de root resuelve y sigue.
www-data..env de producción, reutilizadas como
contraseña del sistema → SSH como usuario con bajos privilegios.root.El tema recurrente en las tres etapas: un proceso privilegiado que confía en datos controlados por el atacante sin validación (un tipo MIME falsificado, una contraseña reutilizada, un nombre de archivo no saneado).
http://localhost:3000.git, python3, curl y ssh disponibles (por defecto en la máquina).chmod +x nexus_privesc.sh
./nexus_privesc.sh -t <TARGET_IP> -u <GITEA_USER> -p <GITEA_PASS> [-d DEPTH]
Ejemplo:
./nexus_privesc.sh -t 10.129.21.192 -u jones -p 'REDACTED' -d 5
/tmp/.exploit_key)... que apunta a
/root/.ssh/authorized_keys, con la clave pública del atacante como contenido.root con la clave implantada.Monitoriza la sincronización en el objetivo con:
tail -f /var/log/template-sync.log
Una ejecución exitosa registra una línea como:
synced: ../../../../../root/.ssh/authorized_keys
DEPTHDEPTH es el número de niveles .. necesarios para subir desde el directorio de
sincronización/staging hasta la raíz del sistema de archivos (/). Si la sincronización tiene éxito en el log pero el
SSH como root falla, el traversal aterrizó en el nivel equivocado — reintenta con -d 4 o
-d 6. El valor exacto puede confirmarse leyendo la ruta de staging en
/etc/gitea/template-sync.py.
Nunca construyas rutas del sistema de archivos a partir de nombres no confiables solo con os.path.join().
Normaliza y confina las rutas, p. ej.:
base = os.path.realpath(base_dir)
dest = os.path.realpath(os.path.join(base, name))
if not dest.startswith(base + os.sep):
raise ValueError("path traversal blocked")
Rechaza nombres de entradas de tree que contengan .., rutas absolutas o separadores de ruta.
Ejecuta el servicio de sincronización bajo una cuenta dedicada con bajos privilegios, no root.
Habilita receive.fsckObjects = true en Gitea para rechazar objetos enviados malformados.
<your name / handle> — para uso educativo en HackTheBox.
| Clase | Path Traversal (CWE-22) → Escalada de Privilegios Locales |
| Componente | gitea-template-sync.service (personalizado /etc/gitea/template-sync.py) |
| Se ejecuta como | root, activado cada ~60s por gitea-template-sync.timer |
| Causa raíz | os.path.join() sobre nombres no saneados tomados de objetos tree de Git |
| Impacto | Usuario autenticado con bajos privilegios → root |
| Flag | Descripción | Por defecto |
|---|
-t | IP objetivo (para el SSH final como root) | — (obligatorio) |
-u | Nombre de usuario de Gitea | — (obligatorio) |
-p | Contraseña de Gitea | — (obligatorio) |
-d | Número de niveles .. para llegar a / desde el directorio de sincronización | 5 |