第 5 章 共享主机

目录

Session 数据的暴露
浏览文件系统

Session 数据的暴露

当在共享主机上时,无法做到像在独立主机上那样安全。这是低廉的使用费用付出的代价。

最明显的弱点是共享主机使用共享的 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 部分的章节,它们也将适用于此。