当你关注于防止源码的暴露时,你的会话数据只同样存在着风险。在默认情况下,SESSION保存在/tmp目录下。这样做在很多情形下是很方便的,其中之一是所有用户都有对/tmp的写入权限,这样Apache同样也有权限进行写入。虽然其他用户不能直接从shell环境读取这些会话文件,但他们可以写一个简单的脚本来进行读取:
01 |
<?php |
02 |
03 |
header( 'Content-Type: text/plain' ); |
04 |
session_start(); |
05 |
06 |
$path = ini_get ( 'session.save_path' ); |
07 |
$handle = dir( $path ); |
08 |
09 |
while ( $filename = $handle ->read()) |
10 |
{ |
11 |
if ( substr ( $filename , 0, 5) == 'sess_' ) |
12 |
{ |
13 |
$data = file_get_contents ( "$path/$filename" ); |
14 |
15 |
if (! empty ( $data )) |
16 |
{ |
17 |
session_decode( $data ); |
18 |
$session = $_SESSION ; |
19 |
$_SESSION = array (); |
20 |
echo "Session [" . substr ( $filename , 5) . "]
" ; |
21 |
print_r( $session ); |
22 |
echo "
--
" ; |
23 |
} |
24 |
} |
25 |
} |
26 |
27 |
?> |
这个脚本在session.save_path所定义的会话文件保存目录中搜索以sess_为前缀的文件。找到文件后,即对它的内容进行解析并用print_r()函数显示它的内容。这样其它开发者就容易地取得了你的用户的会话数据。
解决这个问题的最好方法是把你的会话数据存入用用户名和密码保护的数据库中。由于数据库的访问是受控的,这样就多了一层额外的保护。通过应用前节中提及的技巧,数据库可以为你的敏感数据提供一个安全的存放地,同时你应该保持警惕,你的数据库安全性正变得越来越重要。
为在数据库中保存会话数据,首先需要建立一个数据表:
1 |
CREATE TABLE sessions |
2 |
( |
3 |
id varchar(32) NOT NULL, |
4 |
access int(10) unsigned, |
5 |
data text, |
6 |
PRIMARY KEY (id) |
7 |
); |
如果你使用的是MySQL,则表结构描述如下:
1 |
mysql> DESCRIBE sessions; |
2 |
+--------+------------------+------+-----+---------+-------+ |
3 |
| Field | Type | Null | Key | Default | Extra | |
4 |
+--------+------------------+------+-----+---------+-------+ |
5 |
| id | varchar(32) | | PRI | | | |
6 |
| access | int(10) unsigned | YES | | NULL | | |
7 |
| data | text | YES | | NULL | | |
8 |
+--------+------------------+------+-----+---------+-------+ |
如要使会话数据能保存在此表中,你需要使用session_set_save_handler( )函数来编辑PHP的内建会话机制:
01 |
<?php |
02 |
03 |
session_set_save_handler( '_open' , |
04 |
'_close' , |
05 |
'_read' , |
06 |
'_write' , |
07 |
'_destroy' , |
08 |
'_clean' ); |
09 |
10 |
?> |
以上的六个参数每一个都代表着需要你编写的函数的名称,他们对下面的任务进行处理:
- 打开会话存储
- 关闭会话存储
- 读取会话数据
- 写入会话数据
- 消灭会话数据
- 清除旧会话数据
我有意使用了有意义的名称,这样你可以一下看出它们的目的。命名是任意的,但你可能希望用下划线开头(如此处所示)或其它的命名约定来防止名称冲突。下面是这些函数(使用MySQL)的示例:
01 |
<?php |
02 |
03 |
function _open() |
04 |
{ |
05 |
global $_sess_db ; |
06 |
07 |
$db_user = $_SERVER [ 'DB_USER' ]; |
08 |
$db_pass = $_SERVER [ 'DB_PASS' ]; |
09 |
$db_host = 'localhost' ; |
10 |
11 |
if ( $_sess_db = mysql_connect( $db_host , $db_user , $db_pass )) |
12 |
{ |
13 |
return mysql_select_db( 'sessions' , $_sess_db ); |
14 |
} |
15 |
16 |
return FALSE; |
17 |
} |
18 |
19 |
function _close() |
20 |
{ |
21 |
global $_sess_db ; |
22 |
23 |
return mysql_close( $_sess_db ); |
24 |
} |
25 |
26 |
function _read( $id ) |
27 |
{ |
28 |
global $_sess_db ; |
29 |
30 |
$id = mysql_real_escape_string( $id ); |
31 |
32 |
$sql = "SELECT data |
33 |
FROM sessions |
34 |
WHERE id = '$id' "; |
35 |
36 |
if ( $result = mysql_query( $sql , $_sess_db )) |
37 |
{ |
38 |
if (mysql_num_rows( $result )) |
39 |
{ |
40 |
$record = mysql_fetch_assoc( $result ); |
41 |
42 |
return $record [ 'data' ]; |
43 |
} |
44 |
} |
45 |
46 |
return '' ; |
47 |
} |
48 |
49 |
function _write( $id , $data ) |
50 |
{ |
51 |
global $_sess_db ; |
52 |
53 |
$access = time(); |
54 |
55 |
$id = mysql_real_escape_string( $id ); |
56 |
$access = mysql_real_escape_string( $access ); |
57 |
$data = mysql_real_escape_string( $data ); |
58 |
59 |
$sql = "REPLACE |
60 |
INTO sessions |
61 |
VALUES ( '$id' , '$access' , '$data' )"; |
62 |
63 |
return mysql_query( $sql , $_sess_db ); |
64 |
} |
65 |
66 |
function _destroy( $id ) |
67 |
{ |
68 |
global $_sess_db ; |
69 |
70 |
$id = mysql_real_escape_string( $id ); |
71 |
72 |
$sql = " DELETE |
73 |
FROM sessions |
74 |
WHERE id = '$id' "; |
75 |
76 |
return mysql_query( $sql , $_sess_db ); |
77 |
} |
78 |
79 |
function _clean( $max ) |
80 |
{ |
81 |
global $_sess_db ; |
82 |
83 |
$old = time() - $max ; |
84 |
$old = mysql_real_escape_string( $old ); |
85 |
86 |
$sql = " DELETE |
87 |
FROM sessions |
88 |
WHERE access < '$old' "; |
89 |
90 |
return mysql_query( $sql , $_sess_db ); |
91 |
} |
92 |
93 |
?> |
你必须要在session_start( )之前调用session_set_save_handler( )函数,但你可以在任何地方对这些函数本身进行定义。
这个流程的漂亮之处在于你无须对代码进行编辑或变化使用会话的方式。$_SESSION依然存在,行为依旧,还是由PHP来产生与传递会识标识,对有关会话的配置变更同样还会生效。所有你需要做的只是调用这一个函数(同时建立由它指定的所有函数),PHP就会照顾余下的事情。