January 24, 2008

Phpsec Shared Hosts

5.1 Exposed Session Data

5.2 Browsing the Filesystem

Exposed Session Data

When on a shared host, security simply isn't
going to be as strong as when on a dedicated host. This is one of the tradeoffs
for the inexpensive fee.

One particularly vulnerable aspect of shared
hosting is having a shared session store. By default, PHP stores session data in

/tmp
,
and this is true for everyone. You will find that most people stick with the
default behavior for many things, and sessions are no exception. Luckily, not
just anyone can read session files, because they are only readable by the web
server:

$ ls /tmp

total 12

-rw-------  1  nobody  nobody  123 May 21 12:34 sess_dc8417803c0f12c5b2e39477dc371462

-rw-------  1  nobody  nobody  123 May 21 12:34 sess_46c83b9ae5e506b8ceb6c37dc9a3f66e

-rw-------  1  nobody  nobody  123 May 21 12:34 sess_9c57839c6c7a6ebd1cb45f7569d1ccfc

$

Unfortunately, it is pretty trivial to write a
PHP script to read these files, and because it runs as the user


nobody

(or whatever user the web server uses), it has the necessary privileges.


The
safe_mode
directive can
prevent this and similar safety concerns, but since it only applies to PHP, it
doesn't address the root cause of the problem. Attackers can simply use other
languages.

What's a better solution? Don't use the same
session store as everyone else. Preferably, store them in a database where the
access credentials are unique to your account. To do this, simply use the


session_set_save_handler()

function to override PHP's default session handling with your own PHP functions.

The following code shows a simplistic example for
storing sessions in a database:

<?php

 session_set_save_handler('_open',

                         '_close',

                         '_read',

                         '_write',

                         '_destroy',

                         '_clean');

 

function _open()

{

  global $_sess_db;

 

  $db_user = $_SERVER['DB_USER'];

  $db_pass = $_SERVER['DB_PASS'];

  $db_host = 'localhost';

    

  if ($_sess_db = mysql_connect($db_host, $db_user, $db_pass))

  {

    return mysql_select_db('sessions', $_sess_db);

  }

    

  return FALSE;

}

 

function _close()

{

  global $_sess_db;

    

  return mysql_close($_sess_db);

}

 

function _read($id)

{

  global $_sess_db;

 

  $id = mysql_real_escape_string($id);

 

  $sql = "SELECT data

          FROM   sessions

          WHERE  id = '$id'";

 

  if ($result = mysql_query($sql, $_sess_db))

  {

    if (mysql_num_rows($result))

    {

      $record = mysql_fetch_assoc($result);

 

      return $record['data'];

    }

  }

 

  return '';

}

 

function _write($id, $data)

{   

  global $_sess_db;

 

  $access = time();

 

  $id = mysql_real_escape_string($id);

  $access = mysql_real_escape_string($access);

  $data = mysql_real_escape_string($data);

 

  $sql = "REPLACE 

          INTO    sessions

          VALUES  ('$id', '$access', '$data')";

 

  return mysql_query($sql, $_sess_db);

}

 

function _destroy($id)

{

  global $_sess_db;

    

  $id = mysql_real_escape_string($id);

 

  $sql = "DELETE

          FROM   sessions

          WHERE id = '$id'";

 

  return mysql_query($sql, $_sess_db);

}

 

function _clean($max)

{

  global $_sess_db;

    

  $old = time() - $max;

  $old = mysql_real_escape_string($old);

 

  $sql = "DELETE

          FROM   sessions

          WHERE  access < '$old'";

 

  return mysql_query($sql, $_sess_db);

}

 

?>

This requires an existing table named

sessions
,
whose format is as follows:


mysql> DESCRIBE sessions;

+--------+------------------+------+-----+---------+-------+

 Field   Type              Null  Key  Default  Extra 

+--------+------------------+------+-----+---------+-------+

 id      varchar(32)             PRI                 

 access  int(10) unsigned  YES        NULL           

 data    text              YES        NULL           

+--------+------------------+------+-----+---------+-------+

This database can be created in MySQL with the
following syntax:


CREATE TABLE sessions

(

    id varchar(32) NOT NULL,

    access int(10) unsigned,

    data text,

    PRIMARY KEY (id)

);

Storing your sessions in a database places the
trust in the security of your database. Recall the lessons learned when we spoke
about databases and SQL, because they are applicable here.


Browsing the Filesystem


Just for fun, let's look at a script that browses
the filesystem:


<?php

 

echo "<pre>\n";

 

if (ini_get('safe_mode'))

{

    echo "[safe_mode enabled]\n\n";

}

else

{

    echo "[safe_mode disabled]\n\n";

}

 

if (isset($_GET['dir']))

{

    ls($_GET['dir']);

}

elseif (isset($_GET['file']))

{

    cat($_GET['file']);

}

else

{

    ls('/');

}

 

echo "</pre>\n";

 

function ls($dir)

{

    $handle = dir($dir);

 

    while ($filename = $handle->read())

    {

        $size = filesize("$dir$filename");

 

        if (is_dir("$dir$filename"))

        {

            if (is_readable("$dir$filename"))

            {

                $line = str_pad($size, 15);

                $line .= "<a href=\"{$_SERVER['PHP_SE LF']}?dir=$dir$filename/\">$filename/</a>";

            }

            else

            {

                $line = str_pad($size, 15);

                $line .= "$filename/";

            }

        }

        else

        {

            if (is_readable("$dir$filename"))

            {

                $line = str_pad($size, 15);

                $line .= "<a href=\"{$_SERVER['PHP_SELF']}?file=$dir$filename\">$filename</a>";

            }

            else

            {

                $line = str_pad($size, 15);

                $line .= $filename;

            }

        }

 

        echo "$line\n";

    }

 

    $handle->close();

}

 

function cat($file)

{

    ob_start();

    readfile($file);

    $contents = ob_get_contents();

    ob_clean();

    echo htmlentities($contents);

 

    return true;

}

 

?>

The
safe_mode
directive can
prevent this particular script, but what about one written in another language?


A good solution is to store sensitive data in a
database and use the technique mentioned earlier (where


$_SERVER['DB_USER']

and

$_SERVER['DB_PASS']

contain the access credentials) to protect your database access credentials.


The best solution is to use a dedicated host.


Related Posts by Categories



0 comments: