( ~~~ )
  ))^ ^((
 ((* - *))
   _) (_
 / '--' \     ^
//(_  _)\\   /_\
\\ )__( //   .'
 (( v  ))   (
   \| /\     '-.
    K(  \       )
    |\\  '-._.-'
    ||\\
  *_-P/,P
     '-
Want your PHP application manually audited? Check out Xxor AB's PHP Security Auditing Service

Thursday, July 7, 2011

phpMyAdmin 3.x Multiple Remote Code Executions

This post details a few interesting vulnerabilities I found while relaxing and reading the sourcecode of phpMyAdmin. My original advisory can be found here.

If you would like me to audit your PHP project, check out Xxor's PHP security auditing service. http://www.xxor.se/services/php-security-audit.php

The first vulnerability

File: libraries/auth/swekey/swekey.auth.lib.php
Lines: 266-276
Patched in: 3.3.10.2 and 3.4.3.1
Type: Variable Manipulation
Assigned CVE id: CVE-2011-2505
PMA Announcement-ID: PMASA-2011-5 if (strstr($_SERVER['QUERY_STRING'],'session_to_unset') != false) { parse_str($_SERVER['QUERY_STRING']); session_write_close(); session_id($session_to_unset); session_start(); $_SESSION = array(); session_write_close(); session_destroy(); exit; } Notice the call to parse_str on line 268 that passes the query string as it's first argument. It's missing a second argument. This means that what ever parameters and values are present in the query string will be used as variables in the current namespace. But since the code path that executes the call to parse_str inevitably leads to a call to exit there ain't much to exploit. However the session variables persists between requests. Thus giving us full control of the $_SESSION array.

When reading the code, you might believe that the session gets destroyed. But the call to session_write_close on line 269 saves the modified session, and the call to session_id on line 270 switches session. This could be confuseing when testing in a browser because the call to session_start will send a new cookie instructing the browser to forget about the modified session.

From here on there are numerous XSS and SQL injection vulnerabilities open for attack. But we'll focus on three far more serious vulnerabilities.

The second vulnerability

Patched in: 3.3.10.2 and 3.4.3.1
Type: Remote Static Code Injection
Assigned CVE id: CVE-2011-2506
PMA Announcement-ID: PMASA-2011-6

File: setup/lib/ConfigGenerator.class.php
Lines: 16-78
/** * Creates config file * * @return string */ public static function getConfigFile() { $cf = ConfigFile::getInstance(); $crlf = (isset($_SESSION['eol']) && $_SESSION['eol'] == 'win') ? "\r\n" : "\n"; $c = $cf->getConfig(); // header $ret = 'get('PMA_VERSION') . ' setup script' . $crlf . ' * Date: ' . date(DATE_RFC1123) . $crlf . ' */' . $crlf . $crlf; // servers if ($cf->getServerCount() > 0) { $ret .= "/* Servers configuration */$crlf\$i = 0;" . $crlf . $crlf; foreach ($c['Servers'] as $id => $server) { $ret .= '/* Server: ' . strtr($cf->getServerName($id), '*/', '-') . " [$id] */" . $crlf . '$i++;' . $crlf; foreach ($server as $k => $v) { $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k); $ret .= "\$cfg['Servers'][\$i]['$k'] = " . (is_array($v) && self::_isZeroBasedArray($v) ? self::_exportZeroBasedArray($v, $crlf) : var_export($v, true)) . ';' . $crlf; } $ret .= $crlf; } $ret .= '/* End of servers configuration */' . $crlf . $crlf; } unset($c['Servers']); // other settings $persistKeys = $cf->getPersistKeysMap(); foreach ($c as $k => $v) { $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k); $ret .= self::_getVarExport($k, $v, $crlf); if (isset($persistKeys[$k])) { unset($persistKeys[$k]); } } // keep 1d array keys which are present in $persist_keys (config.values.php) foreach (array_keys($persistKeys) as $k) { if (strpos($k, '/') === false) { $k = preg_replace('/[^A-Za-z0-9_]/', '_', $k); $ret .= self::_getVarExport($k, $cf->getDefault($k), $crlf); } } $ret .= '?>'; return $ret; } On line 42 in this file a comment is created to show some additional information in a config file. We can see that the output of the call to $cf->getServerName($id) is sanitized to prevent user input from closing the comment. However $id, the key of the $c['Servers'] array, is not. So if we could rename a key in this array we could close the comment and inject arbitrary PHP code.
On line 26 the $c array is created from a call to $cf->getConfig().

File: libraries/config/ConfigFile.class.php
Lines: 469-482
/** * Returns configuration array (full, multidimensional format) * * @return array */ public function getConfig() { $c = $_SESSION[$this->id]; foreach ($this->cfgUpdateReadMapping as $map_to => $map_from) { PMA_array_write($map_to, $c, PMA_array_read($map_from, $c)); PMA_array_remove($map_from, $c); } return $c; } Bingo! The $c array is derived from the $_SESSION array hence we could have full control of its contents by utilizing the first vulnerability. Now we can inject arbitrary PHP code that will be saved into the file config/config.inc.php. Then we would just browse to this file and the webserver would executed it.

This vulnerability requires one specific condition. The config directory must have been left in place after the initial configuration. This is something advised against and hence a majority of servers wont be susceptible to this attack. Therefor we'll check out a third and a fourth vulnerability.

The third vulnerability

Patched in: 3.3.10.2 and 3.4.3.1
Type: Authenticated Remote Code Execution
Assigned CVE id: CVE-2011-2507
PMA Announcement-ID: PMASA-2011-7

File: server_synchronize.php
Line: 466
$trg_db = $_SESSION['trg_db']; Line: 477 $uncommon_tables = $_SESSION['uncommon_tables']; Line: 674 PMA_createTargetTables($src_db, $trg_db, $src_link, $trg_link, $uncommon_tables, $uncommon_table_structure_diff[$s], $uncommon_tables_fields, false); File: libraries/server_synchronize.lib.php
Lines: 613-631 function PMA_createTargetTables($src_db, $trg_db, $src_link, $trg_link, &$uncommon_tables, $table_index, &$uncommon_tables_fields, $display) { if (isset($uncommon_tables[$table_index])) { $fields_result = PMA_DBI_get_fields($src_db, $uncommon_tables[$table_index], $src_link); $fields = array(); foreach ($fields_result as $each_field) { $field_name = $each_field['Field']; $fields[] = $field_name; } $uncommon_tables_fields[$table_index] = $fields; $Create_Query = PMA_DBI_fetch_value("SHOW CREATE TABLE " . PMA_backquote($src_db) . '.' . PMA_backquote($uncommon_tables[$table_index]), 0, 1, $src_link); // Replace the src table name with a `dbname`.`tablename` $Create_Table_Query = preg_replace('/' . PMA_backquote($uncommon_tables[$table_index]) . '/', PMA_backquote($trg_db) . '.' .PMA_backquote($uncommon_tables[$table_index]), $Create_Query, $limit = 1 ); The variables $uncommon_tables[$table_index] and $trg_db are derived from the $_SESSION array. By utilizing the first vulnerability we can inject what ever we want into both the first and the second argument of the function preg_replace on lines 627-631. In a previous post to this blog I've detailed how this condition can be turned into a remote code execution. Basicly we can inject the "e" modifier into the regexp pattern which causes the second argument to be executed as PHP code.

This vulnerability have two major restrictions from an attackers perspective. First the Suhosin patch that completly defends against this type of attack. Second, this piece of code can only be reached if we're authenticated. So to exploit it we would need to have previous knowledge of credentials to an account of the database that phpMyAdmin is set up to manage. Except for some obscure configurations that allows us to bypass this restriction.

Since the Suhosin patch is pretty popular, and for example compiled by default in OpenBSD's PHP packages, it's worth exploring a fourth vulnerability.

The fourth vulnerability

Patched in: 3.3.10.2 and 3.4.3.1
Type: Path Traversal
Assigned CVE id: CVE-2011-2508
PMA Announcement-ID: PMASA-2011-8

File: libraries/display_tbl.lib.php
Lines: 1291-1299 if ($GLOBALS['cfgRelation']['mimework'] && $GLOBALS['cfg']['BrowseMIME']) { if (isset($GLOBALS['mime_map'][$meta->name]['mimetype']) && isset($GLOBALS['mime_map'][$meta->name]['transformation']) && !empty($GLOBALS['mime_map'][$meta->name]['transformation'])) { $include_file = $GLOBALS['mime_map'][$meta->name]['transformation']; if (file_exists('./libraries/transformations/' . $include_file)) { $transformfunction_name = str_replace('.inc.php', '', $GLOBALS['mime_map'][$meta->name]['transformation']); require_once './libraries/transformations/' . $include_file; This fourth vulnerability is a directory traversal in a call to require_once which can be exploited as a local file inclusion. The variable $GLOBALS['mime_map'][$meta->name]['transformation'] is derived from user input. For example, by setting $GLOBALS['mime_map'][$meta->name]['transformation'] to "../../../../../../etc/passwd" the local passwd-file could show up.

This vulnerability can only be reached if we're authenticated and requires that the transformation feature is setup correctly in phpMyAdmin's configuration storage. However, the $GLOBALS['cfgRelation'] array is derived from the $_SESSION array. Hence the variable $GLOBALS['cfgRelation']['mimework'] used to check this can be modified using the first vulnerability.

File: libraries/display_tbl.lib.php
Lines: 707-710 if ($GLOBALS['cfgRelation']['commwork'] && $GLOBALS['cfgRelation']['mimework'] && $GLOBALS['cfg']['BrowseMIME'] && ! $_SESSION['tmp_user_values']['hide_transformation']) { require_once './libraries/transformations.lib.php'; $GLOBALS['mime_map'] = PMA_getMIME($db, $table); } And the fact that $GLOBALS['mime_map'] is conditionally initialized together with the fact that phpMyAdmin registers all request variables in the global namespace (blacklists some, but not mime_map) allows us to set $GLOBALS['mime_map'][$meta->name]['transformation'] to whatever we want, even when the transformation feature is not setup correctly.

Summary

  • If the config folder is left in place, phpMyAdmin is vulnerable.

  • If an attacker has access to database credentials and the Suhosin patch is not installed, phpMyAdmin is vulnerable.

  • If an attacker has access to database credentials and knows how to exploit a local file inclution, phpMyAdmin is vulnerable.

Exploits


Here are some exploits that have appeard so far, sorted in chronological order.

phpMyAdmin3 (pma3) Remote Code Execution Exploit written in python by wofeiwo exploiting vulnerability 1 and 2.
http://www.exploit-db.com/exploits/17510/

phpMyAdmin 3.x preg_replace RCE POC written in php by Mango exploiting vulnerability 1 and 3. This isn't really an exploit, just a POC.
http://ha.xxor.se/2011/07/phpmyadmin-3x-pregreplace-rce-poc.html

phpMyAdmin 3.x Swekey RCI Exploit written in php by Mango exploiting vulnerability 1 and 2.
http://ha.xxor.se/2011/07/phpmyadmin-3x-swekey-rci-exploit.html

An extra noteworthy exploit is this one created by M4g exploiting vulnerability 1. He paired the first vulnerability with a rougthly one year old bug in the PHP core. The PHP Session Serializer Session Data Injection Vulnerability found by Stefan Esser.
http://snipper.ru/view/103/phpmyadmin-33102-3431-session-serializer-arbitrary-php-code-execution-exploit/
Or for those of us who can't read Russian, use Google translate.

17 comments:

  1. In The first vulnerability

    If I inject Arbitrate Session via $_SERVER['QUERY_STRING'] why session doesn't reset after session_start()

    ReplyDelete
  2. @Anonymous

    Your original session is modified by:
    parse_str($_SERVER['QUERY_STRING']);

    Then it switches session with:
    session_id($session_to_unset);

    Then the new session is reset using:
    session_start();

    Your original session remains and is modified.

    Don't try to test this issue in a browser. Write a script to handle the cookies and requests yourself.

    ReplyDelete
  3. @Mango
    How to modify that session with $_SERVER['QUERY_STRING']

    is it right way?

    ./swekey.auth.lib.php?_SESSION['attribute']=myvalue&session_to_unset=null

    but why i can't modify Session

    ReplyDelete
  4. @Josh

    ./index.php?_SESSION[attribute]=myvalue&session_to_unset=null

    It cant be done with a browser. Since you need to retain your old cookies when it switches session.

    ReplyDelete
  5. what about the directory traversal bug how is that usable ? an example link would be gladly welcomed.

    ReplyDelete
  6. @Anonymous2

    It's usable as a LFI.

    If the transformation feature in phpMyAdmin is setup correctly, you can insert the path in it's interface. Otherwise you need to write a script to use the first vulnerability.

    ReplyDelete
  7. can you give me an e-mail of yours or something like that to talk to you faster or this comment area should be more then enough ?

    ReplyDelete
  8. Finns du på irc någonstans? Mail är så 1999 :p

    ReplyDelete
  9. Hi Mango,

    Do you mind providing a quick PoC for the Swekey_login() issue? Looks completely unexploitable to me. Do you not see the $_SESSION superglobal being manually destroyed, on top of being destroyed with the PHP session functionality?

    $_SESSION = array();

    I also do not believe the preg_replace() issue to be actually exploitable due to the PMA_Backquote() calls tampering with the data, as I explained here: http://0x6a616d6573.blogspot.com/2011/07/phpmyadmin-fud.html

    I would love to hear your input on these issues, and am wondering if you have actually successfully exploited either the PMA_createTargetTables() or Swekey_login() bugs.

    Regards,

    ~James

    ReplyDelete
  10. @0x6a616d6573

    POC released: http://ha.xxor.se/2011/07/phpmyadmin-3x-pregreplace-rce-poc.html

    ReplyDelete
  11. @Anonymous

    Har lite tight tid vid datorn i veckan. Droppa mig ett mail med en kanal/server så ska jag titta in är ja kan.

    ReplyDelete
  12. It seems it's not the Suhosin patch which will defend against the /e modifier attack, but rather the extension combined with the following, off-by-default option:

    http://www.hardened-php.net/suhosin/configuration.html#suhosin.executor.disable_emodifier

    Interesting vulnerabilities. Thanks for sharing.

    ReplyDelete
  13. What is SESSION That can modify $GLOBALS['cfgRelation']

    ReplyDelete
  14. @Anonymous
    The exploit utilizes a null byte injection in preg_replace which Suhosin patches. I don't believe that can be turned off.

    @Anonymous
    Check out line 98 in
    libraries\config\ConfigFile.class.php.
    $this->id = 'ConfigFile' . $GLOBALS['server'];

    ReplyDelete
  15. hello i trying to scan in wild but nothing seems to be vulnerable all pma servers is 2.* instaled on servers anywoane any ideea ? a dork for PMA 3.*?

    ReplyDelete
  16. Awful breath of air may well be one of many main puppy peeves inside interpersonal interaction. This particular full difficulty connected with bad breath of air is a entire bummer as it can not be attended to without annoying anybody concerned.Kyäni

    ReplyDelete