Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
From: Shaun Clowes (shamanoptushome.com.au)
Date: Mon Jul 02 2001 - 09:41:53 CDT

  • Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

    Secure Reality Pty Ltd. Security Advisory #10 (SRADV00010)

    Remote command execution vulnerabilities in SquirrelMail


    Versions up to and including 1.0.4

    SquirrelMail is an amazingly easy to use, good looking and functional web
    mail system written in PHP.

    In versions specified SquirrelMail 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'.

    Remote command execution (with privileges as above)

    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

    As with all the advisories released in conjunction with the paper above I'm
    going to describe this problem in gross detail, from finding the hole to
    exploiting it (sidestepping various annoying barriers along the way).

    The problem is initially spotted with a trivial grep of the source. The
    following line of code in load_prefs.php seems suspicious:

    39 require("$chosen_theme");

    The require() function tells PHP to read in the file specified in the
    variable $chosen_theme and interpret it as though it were PHP code. If the
    attacker can affect $chosen_theme (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 higher in the file we get a feel for what load_prefs.php is meant to

    8 ** Loads preferences from the $username.pref file used by almost
    9 ** every other script in the source directory and alswhere.

    Ok, so its a file that is typically include()d by other PHP scripts that
    loads the users preferences (where user is determined by $username). This is
    looking really good. This file is a library code file, its not ever meant to
    be requested by a remote user, but it has the correct extension (.php) to be
    parsed as PHP code so a remote attacker can request it. Scripts like this
    are typically low hanging fruit since the fact that they're meant to be
    included by other scripts often means they have a certain 'context' they're
    meant to work in. Without this context they're often easy to exploit.

    Looking from the top of the file at various housekeeping code:

    14 if (!isset($config_php))
    15 include("../config/config.php");
    16 if (!isset($prefs_php))
    17 include("../functions/prefs.php");
    18 if (!isset($plugin_php))
    19 include("../functions/plugin.php");
    21 $load_prefs_php = true;
    22 if (!isset($username))
    23 $username = '';
    24 checkForPrefs($data_dir, $username);

    Ok, lines 14 to 19 are interesting. The code tries to determine if its
    environment has been well established (that is, important code this page
    relies upon has already been included). However, this is not a secure way to
    determine if the environment has been well constructed, an attacker can
    cause $config_php, $prefs_php or $plugin_php to be set by submitting them as
    form variables and thereby cause the required code not to be included.

    Having tried to construct a sane environment the code goes on to set a flag
    indicating it has been included (later scripts check this variable before
    trying to include it, in the same way that it checks for config.php etc). Th
    e code then goes on to insure that $username is set to something (note that
    no code to authenticate $username is evident yet). The script then calls
    checkForPrefs with $data_dir (which is a configuration variable normally set
    in config.php) and $username (the unauthenticated user the remote requestor
    is claiming to be).

    checkForPrefs() is a function defined in ../functions/prefs.php. The
    following is the relevant code:

    116 /** This checks if there is a pref file, if there isn't, it will
    117 create it. **/
    118 function checkForPrefs($data_dir, $username) {
    119 $filename = "$data_dir$username.pref";
    120 if (!file_exists($filename)) {
    121 if (!copy("$data_dir" . "default_pref", $filename)) {
    122 echo _("Error opening ") ."$filename";
    123 exit;
    124 }
    125 }
    126 }

    Interesting. When Squirrelmail is first installed the user needs to
    configure a data directory (that must be writable by Squirrelmail).
    Squirrelmail stores attachments to be downloaded, address book details and
    user preferences in subdirectories of this directory. This code takes the
    unauthenticated username and checks if a preferences file exists for this
    user, if it doesn't its created. This will come in very handy later on.

    So back in load_prefs.php:

    26 $chosen_theme = getPref($data_dir, $username, "chosen_theme");
    27 $in_ary = false;
    28 for ($i=0; $i < count($theme); $i++){
    29 if ($theme[$i]["PATH"] == $chosen_theme) {
    30 $in_ary = true;
    31 break;
    32 }
    33 }
    34 if (!$in_ary) {
    35 $chosen_theme = "";
    36 }

    This code reads the users preference of 'theme' (Squirrelmail has a
    themeable user interface) into $chosen_theme. It then loops through the
    $theme[] array and determines if $chosen_theme matches one of those themes.
    If it doesn't its set to nothing.

    The $theme array is an array of valid themes set in config.php:

    46 $theme[0]["PATH"] = "../themes/default_theme.php";
    47 $theme[0]["NAME"] = "Default";
    48 $theme[1]["PATH"] = "../themes/plain_blue_theme.php";
    49 $theme[1]["NAME"] = "Plain Blue";
    50 $theme[2]["PATH"] = "../themes/sandstorm_theme.php";
    51 $theme[2]["NAME"] = "Sand Storm";
    52 $theme[3]["PATH"] = "../themes/deepocean_theme.php";
    53 $theme[3]["NAME"] = "Deep Ocean";
    54 $theme[4]["PATH"] = "../themes/slashdot_theme.php";

    This makes it all look much harder, an attacker can have a preferences file
    created (checkForPrefs() does that), but what then? They need to be able to
    change their "chosen_theme" preference to effect the require() we noted at
    the start of all this but if its not set to an element of the theme array
    (of which none are interesting) it'll just be unset.

    As it turns out it takes a rather convoluted path to modify the preferences
    file chosen_theme without authenticating. It should be clear how you could
    do so by the end of this advisory however. Anyway, there's much jucier lines
    of attack if we look further into load_prefs.php.

    38 if ((isset($chosen_theme)) && (file_exists($chosen_theme))) {
    39 require("$chosen_theme");
    40 } else {
    41 if (file_exists($theme[0]["PATH"])) {
    42 require($theme[0]["PATH"]);
    43 } else {

    Ignoring the chosen_theme we spot the $theme[0]['path'] code. This should
    stick out like a sore thumb. Recall that the theme array comes from the
    config.php file. This file is include()d at the beginning of load_prefs.php
    but ONLY if $config_php is not configured. So the immediately obvious attack
    is to set the following as form input:

    username = evilattacker
    config_php = true
    theme[0][PATH] = /etc/passwd

    Which presumably return the password file to the attackers web browser.
    Unfortunately this attack is foiled and typically results in an error
    message something like 'Fatal error: Call to undefined function: _() in
    /home/html/squirrelmail-1.0.4/functions/prefs.php on line 122' is generated.
    This is caused by the checkForPrefs() routine. It's checking if the user's
    preference file exists (as we saw earlier) but because we caused config.php
    not to be included when it constructs the filename of
    '$data_dir$username.pref' it ends up with just '<username>.pref'. Since this
    is a file in the root directory it will never be found so it tries to create
    it and when this operation fails the script aborts.

    The attacker now needs to provide the $data_dir variable themselves. It
    should be hard for an attacker to guess the location but unfortunately one
    of the scripts leaks this information. Looking at the script

    19 if (!isset($page_header_php))
    20 include('../functions/page_header.php');

    This is just one of the includes specified at the top of options_display.php
    (which normally displays a users preferences). Its performed before any sort
    of authentication.

    In the file page_header.php (which fairly obviously normally provides a
    standard header for all the pages):

    21 // Check to see if gettext is installed
    22 $headers_sent=set_up_language(getPref($data_dir, $username,

    The above lines of code try to setup the language of the user. The call the
    following code from prefs.php:

    13 function getPref($data_dir, $username, $string) {
    14 $filename = "$data_dir$username.pref";
    15 if (!file_exists($filename)) {
    16 printf (_("Preference file %s not found. Exiting abnormally"),
    17 exit;
    18 }

    Looking at the above, before any authentication the page looks for
    '$data_dir$username.pref', if that isn't found it nicely tells us the file
    it was looking for but couldn't find. So, if an attacker requests the page
    without any username specified at all they receive a message like
    'Preference file /var/squirrelmail/data/.pref not found. Exiting
    abnormally'. $data_dir is obviously the bit before '.pref'.

    The above makes the attack specified earlier possible with the following

    username = evilattacker
    config_php = true
    theme[0][PATH] = /etc/passwd
    data_dir = <dir as found>

    If an attacker makes a request like
    they get a copy of the remote machines /etc/passwd file delivered to their
    web browser.

    Presumably they're not going to be happy with just this level of access and
    would like to further exploit the problem to gain remote command execution
    privileges. This would normally be a relatively trivial exercise since the
    attacker can pick any file they wish on the remote machine and have it
    interpreted as PHP code. Note that we can't use a remote files attack here
    because the code does a file_exists() on the file before including it and
    file_exists() does not work with the PHP remote files functionality.

    The attacker just needs to get code of their chosing into a file on the
    remote machine. As discussed in 'A Study In Scarlet' there are many ways to
    do this. My first attempt used file upload but it failed as there appear to
    be some weird bugs in my version of PHP (4.0.4pl1) with uploading a file
    into a field with a two dimensional array name, this may well have been
    fixed since then. Anyway, trying to be general the attacker will look for an
    alternate way to get code into a file on the remote machine.

    I chose the most obvious avenue, the preferences file. Squirrelmail has been
    kind enough to create this file for us, its been kind enough to tell us
    which directory its in too so we know its absolute filename. The file looks
    like the following:


    All we need to do is get PHP code into this file without being forced to
    authenticate. This ability is provided by the options_order.php script.
    options_order.php normally allows a user to specify the order in which key
    fields of an email (Subject, From, To etc) they'd like displayed and in what
    order they should be displayed. As can be seen above each field is
    identified by a field number (order<fieldnumber>=<place in display>'.
    However options_display doesn't go to any real effort to verify what a user

    83 } else if ($method == 'add' && $add) {
    84 $index_order[count($index_order)+1] = $add;
    85 }
    87 if ($method) {
    88 for ($i=1; $i <= count($index_order); $i++) {
    89 setPref($data_dir, $username, "order$i", $index_order[$i]);
    90 }
    91 }

    If the form input contains a 'method' variable set to 'add' the script
    assumes the user has decided to include another field in the display. The
    then adds the field to the end of the list of fields to be displayed by
    placing its number (which it assumes is in $add) to the end of the
    preferences list. The problem is that $add is a form variable that is
    normally set via a drown down list in the preferences page to a valid field
    number but if an attacker ignores the form and sets it themselves it can
    easily be anything. Once the new field has been added all the fields are
    saved in the preferences file.

    An attacker can therefore specify something the following input and have PHP
    code written to a known file:

    username = evilattacker
    method = add
    add = "<?php passthru("/bin/ls /etc");"

    When the attacker makes the request like
    ethod=add&add=<?php%20passthru("/bin/ls%20/etc");%20?> the code is written
    to '$data_dir$username.pref'.

    Now the attacker can execute the same attack as specified above to retrieve
    the password file to execute the code they just sent. The following request:


    would then result in a listing of the /etc directory being returned to the
    attacker in their web browser. Obviously any command could be specified and
    further exploit code could be uploaded and executed as described in 'A Study
    In Scarlet'.

    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

    Later versions of SquirrelMail correct this problem. Please download a
    version above 1.0.5 from:

    Our thanks to Luke Ehresman, Gustav Foseid and all the Squirrelmail
    developers for their professional manner in confirming and fixing this

    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.