Guide de Sécurité PHP: Hôtes partagés (Shared Hosts)


< PrécédentSuivant >A propos deLes sessions

Données de session exposées

Sur un hôte partagé, la sécurité ne sera simplement pas aussi forte que sur un serveur dédié. C'est l'un des inconvénients de la redevance bon marché (inexpensive fee).

Un aspect particulièrement vulnérable du hosting partagé est d'avoir un stockage de session (session store) partagé. Par défaut, PHP stocke les données de session dans /tmp, et c'est vrai pour tout le monde. Vous trouverez que la plupart des personnes se tiennent aux comportement par défaut pour beaucoup de choses, et les sessions ne font pas exception. Heureusement, tout le monde ne peut pas lire les fichiers de session, car ils ne sont lisibles que par le serveur web:

$ 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
$

Malheureusement, il est assez trivial de rédiger un script PHP pour lire ces fichiers, et puisqu'il tourne sous l'utilisateur nobody (ou tout utilisateur sous lequel tourne le serveur web) web), il possède les privilèges nécessaires.

La directive safe_mode peut empêcher cela et d'autres soucis similaires de sécurité. Mais vu qu'elle ne s'applique qu'à PHP, elle ne s'attaque pas à la cause à l'origine du problème. Les attaquants peuvent simplement employer d'autres langages.

Quelle serait une meilleure solution? N'utilisez pas le même stockage de session que tous les autres. De préférence, stockez-le dans une base de données dont les autorisations d'accès sont propres à votre compte utilisateur (account). Pour cela, utilisez simplement la fonction session_set_save_handler() pour écraser la gestion de session par défaut de PHP par vos propres fonctions PHP.

Le code suivant montre un exemple simpliste pour stocker les sessions dans une base de données:

<?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);
}

?>

Ceci requiert une table existante nommée sessions, dont le format est le suivant:

mysql> DESCRIBE sessions;
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id     | varchar(32)      |      | PRI |         |       |
| access | int(10) unsigned | YES  |     | NULL    |       |
| data   | text             | YES  |     | NULL    |       |
+--------+------------------+------+-----+---------+-------+

Cette base de données peut être créée en MySQL avec la syntaxe suivante:

CREATE TABLE sessions
(
    id varchar(32) NOT NULL,
    access int(10) unsigned,
    data text,
    PRIMARY KEY (id)
);

Stocker vos sessions dans une base de données place la confiance dans la sécurité de votre base de données. Rappelez-vous des leçons apprises lorsque nous parlions des bases de données et de SQL, car elles s'appliquent ici.

Naviguer dans le système de fichiers

Juste pour le fun, regardons un script qui navigue dans le système de fichier:

<?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;
}

?>

La directive safe_mode peut empêcher ce script particulier de fonctionner, mais que dire d'un script rédigé dans un autre langage?

Une bonne solution consiste à stocker les données sensibles dans une base de données et d'utiliser la technique mentionnée plus tôt (où $_SERVER['DB_USER'] et $_SERVER['DB_PASS'] contiennent les autorisations d'accès) pour protéger vos autorisations d'accès à la base de données.

La meilleure solution est d'utiliser un serveur dédié.


< PrécédentSuivant >A propos deLes sessions