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