当在共享主机上时,无法做到像在独立主机上那样安全。这是低廉的使用费用付出的代价。
最明显的弱点是共享主机使用共享的 session 存储。通常,PHP 在 /tmp(译注:针对*nix系统而言)中存储 session 数据,并且对虚拟主机上的每个用户都是如此。你可以发现多数人在多数地方使用默认设置,而 sessions 也不例外。幸运的是,不是每个人都可以读取 session 文件,因为它们只可以被 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
$
不幸的是,编写读取这些文件的 PHP 脚本是非常容易的。并且由于 PHP 脚本运行在用户 nobody (或者任何 WEB 服务器运行的用户),它拥有相应的权限。
设置 safe_mode 可以避免这个安全隐患,但是它只作用于 PHP,不能从根本上解决这个问题。使用其他语言可以非常简单的进行攻击。
那么更好的解决办法是什么?不要对虚拟主机上的每个站点使用相同的 session 存储位置。更好的方式,当数据库帐户是你独有的时候,在数据库中存储它们。要做到这个,使用
session_set_save_handler() 函数使用你自己的函数改写 PHP 默认的 session 处理方式。
下面的代码简单演示了在数据库中存储 sessions:
<?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);
}
?>
这需要事先创建如下格式,名为 sessions 的数据表:
mysql> DESCRIBE sessions;
+--------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| id | varchar(32) | | PRI | | |
| access | int(10) unsigned | YES | | NULL | |
| data | text | YES | | NULL | |
+--------+------------------+------+-----+---------+-------+
可以使用下面的语句创建这个数据库:
CREATE TABLE sessions
(
id varchar(32) NOT NULL,
access int(10) unsigned,
data text,
PRIMARY KEY (id)
);
在数据库中保存 sessions 就意味着把安全交由数据库负责。复习数据库和 SQL 部分的章节,它们也将适用于此。