Ghidul Securitatii PHP: Shared Hosts


< InapoiInainte >DespreSesiuni

Exposed Session Data

Cand esti pe un shared host, securitatea nu poate fi la fel de puternica cum ar fi pe un dedicated host. Este unul din lucrurile care vin cu un pret mai ieftin.

O vulnerabilitate importanta o reprezinta faptul ca session store-ul este folosit de toate site-urile de pe host. Ca default, PHP tine session data in /tmp, acest lucru fiind valabil pentru toata lumea. Majoritatea oamenilor lasa lucrurile setate cu setarile default, si sesiunile nu reprezinta o exceptie. Din fericire, nu oricine poate citi fisierele de session, pentru ca acestea sunt accesibile doar serverului.

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

Totusi, este usor sa scrii un script PHP care sa citeasca aceste fisiere. Pentru ca PHP executa cu privilegiile userului nobody (sau orice foloseste serverul), are acces la ele.

Directiva safe_mode poate preveni acest lucru, si altele similare, dar se refera doar la PHP si nu rezolva problema total pentru ca atacatorul poate folosi alt limbaj.

O solutie mai buna? Nu folosi acelasi session store ca restul lumii. Mai bine foloseste baza de date, la care doar tu ai acces. Pentru a face acest lucru, foloseste functia session_set_save_handler() pentru a schimba default-urile cu functii specificate de tine.

Urmatorul cod este un exemplu simplistic de salvare a sesiunilor intr-o baza de date:

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

?>

Avem nevoie de un tablel numit sessions, care arata in felul urmator:

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

Tabelul poate fi creat in MySQL cu urmatoarea sintaxa:

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

Pastrarea sesiunilor in baza de date inseamna ca securitatea aplicatiei depinde de securitatea bazei de date. Tot ce am discutat la baze de date si SQL este important si aici.

Citirea Filesystem-ului

Sa consideram urmatorul script care citeste filesystemul:

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

?>

Directiva safe_mode poate preveni acest script, dar nu si un script scris in alt limbaj.

O solutie buna este sa tii datele importante in baza de date si sa folosesti tehnicile pe care le-am discutat pentru a proteja credentialele de acces.

Cea mai buna solutie o reprezinta un host dedicat.


< InapoiInainte >DespreSesiuni