Thursday, April 28, 2011

Joomla 1.6.0 Analysis and Exploitation

Last month, a critical SQL Injection vulnerability was discovered in Joomla 1.6.0. , and this past weekend I finally got around to taking a closer look at this issue, and working on an exploit. Originally I was going to write an exploit for both the 1.6.0 issue, as well as the 1.5.21 issue, and have the exploit select a target based on the version detected, but the original advisory was erroneous. It seems that the author of the original advisory used burp suite to discover the bug, and did not actually understand what was really happening at all, under the hood.

In the above image, we see two PoC examples given. They DO both trigger an SQL error, but this is simply due to the field(s) being null as the ordering paramaters pass through the Joomla "word" and "cmd" filter types, and are stripped of almost all special characters. (As seen in the example below)

Joomla <= 1.5.21 Vulnerable function
1:  function _buildQuery()  
2:  {  
3:       $filter_order          = $this->getState('filter_order');  
4:       $filter_order_dir      = $this->getState('filter_order_dir');  
5:       $filter_order          = JFilterInput::clean($filter_order, 'cmd');  
6:       $filter_order_dir      = JFilterInput::clean($filter_order_dir, 'word');  
7:       // We need to get a list of all weblinks in the given category  
8:       $query = 'SELECT *' .  
9:            ' FROM #__weblinks' .  
10:            ' WHERE catid = '. (int) $this->_id.  
11:            ' AND published = 1' .  
12:            ' AND archived = 0'.  
13:            ' ORDER BY '. $filter_order .' '. $filter_order_dir .', ordering';  
14:       return $query;  
15:  }  

As a  result this issue is simply not exploitable, so I was only able to create a working exploit for 1.6.0. The reason 1.6.0 is actually exploitable is due to the "string" filter being used in the example code below, which allows us to use certain characters necessary for exploitable SQL Injection.

Joomla 1.6.0 Vulnerable function
1:  /**  
2:   * Build the orderby for the query  
3:   *  
4:   * @return     string     $orderby portion of query  
5:   * @since     1.5  
6:   */  
7:  protected function _buildContentOrderBy()  
8:  {  
9:        $app              = JFactory::getApplication('site');  
10:       $params           = $this->state->params;  
11:       $itemid           = JRequest::getInt('id', 0) . ':' . JRequest::getInt('Itemid', 0);  
12:       $filter_order     = $app->getUserStateFromRequest('com_content.category.list.' . $itemid . '.filter_order', 'filter_order', '', 'string');  
13:       $filter_order_Dir = $app->getUserStateFromRequest('com_content.category.list.' . $itemid . '.filter_order_Dir', 'filter_order_Dir', '', 'cmd');  
14:       $orderby = ' ';  
15:       if ($filter_order && $filter_order_Dir) {  
16:            $orderby .= $filter_order . ' ' . $filter_order_Dir . ', ';  
17:       }  
18:       $articleOrderby   = $params->get('orderby_sec', 'rdate');  
19:       $articleOrderDate = $params->get('order_date');  
20:       $categoryOrderby  = $params->;def('orderby_pri', '');  
21:       $secondary        = ContentHelperQuery::orderbySecondary($articleOrderby, $articleOrderDate) . ', ';  
22:       $primary          = ContentHelperQuery::orderbyPrimary($categoryOrderby);  
23:       $orderby .= $primary . ' ' . $secondary . ' a.created ';  
24:       return $orderby;  
25:  }  

In order to exploit this issue I use a sub query to ask boolean style questions. If the answer is true I execute BENCHMARK() and look for a delay, thus confirming the answer to our question. It should be noted that another useful exploitation approach (that is not as reliable in this situation, thus the reason i am using a sub query) when dealing with "ORDER BY" injections, is to use the sort order to determine the answer to your questions.

STEP 01:
Determine if the host is vulnerable by extracting  the version, and performing a simple compare. This step can be skipped if needed, as all the benchmark tests will fail if the host is not vuln.

STEP 02:
Trigger an SQL error in order to confirm that the host is vulnerable, and also extract the database table prefix for reliability purposes. SQL Errors are by default shown to unauthenticated users it seems, which helps make exploitation much more reliable.

STEP 03:
Prepare for BENCHMARK() enumeration by establishing both normal, and delayed request medians.

STEP 04:
Find a valid admin id in the "user_usergroup_map" table, which maps admin users to the group_id 8. Since this SQL Injection is completely blind we need to ask "boolean" questions. As a result we start at 42 (which is hard coded within Joomla as the base uid), and step through 10 at a time until we basically "pass up" a valid id. After the id has been passed, we step back 10 and start incrementing by one until we find the uid. This process repeats with the second record in the table if admin access is unable to be gained with the first uid that is enumerated.

STEP 05:
Extract the admin password hash via single character enumeration.

STEP 06:
Extract the admin password salt via single character enumeration. We use "SUBSTRING(#{sqlf},#{i},1) LIKE BINARY CHAR(#{cchr.ord})" here vs "SUBSTRING = FOO" so that we can distinguish between case sensitive characters reliably.

STEP 07:
Cookie based authentication, and password reset functionality seem to be limited to standard, non admin, users. So we attempt to crack the password hash using a wordlist since it seems we can not use the SQL Injection to further escalate privileges, unless the mysql user has file access. (INTO OUTFILE) The encryption format used by Joomla can be represented in the following pseudo code:

md5(PASS + SALT)

STEP 08:
Extract the administrator username via single character enumeration after determining the length of the username.

STEP 09:
Authenticate as an administrator.

STEP 10:
Upload wrapper and execute the payload.

The above list is rather brief, but you can view the exploit source code for further comments, and insight in to exploiting this bug. The following exploit is in ruby, written for the Metasploit Framework.

Joomla 1.6.0 SQL Injection -> PHP Code Execution (Metasploit)